:- module(dbserver, 
	[
	    db_handler/3, 
	    db_maintenance/3, 
	    amos_reviewer_access/2,
	    dying/2,
	    die/1
	],
	[assertions, basicmodes, regtypes, runtime_ops, 
	 pillow, persdb, persdb_mysql_op]).

:- comment(title,"An active module based dbserver with interface to
                  SQL remote DBs").

:- comment(author,"Jose Manuel Gomez Perez").

%%%%%% A server (an active module) to manage persistent databases %%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%% Needed modules %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- use_module(engine(internals), [initialization/1]).
:- use_module(library(file_utils), [file_to_string/2]).
:- use_module(library(lists)).
:- use_module(library(system)).
:- use_module(library(aggregates)).
:- use_module(library(dynamic)).
:- use_module(library(streams)).
:- use_module(library(read)).
:- use_module(library(write)).
:- use_module(library(terms), [atom_concat/2]).
:- use_module(library(patterns)).
:- use_module(library(errhandle)).

% AMOS specific database check module and other stuff:
:- use_module(db_update). 
:- use_module(db(database_impl)).
:- use_module(db(pkg_list)).
:- use_module(search(utils), [log_message/1, remove_dir/2]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%% Database description  and html format predicates %%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% All of these data predicates correlate with files of the same name with 
% extension .pl stored in directory DBTYPES. All of them are used to 
% configure a certain aspect of the system.

:- dynamic html_format/5.                    % Defines html specific types
:- data html2sql/2.                          % Defines translation of html
                                             % types into canonical SQL types
:- data sql2html/3.                          % Viceversa
:- data option_table_size/2.                 % Defines sizes of input forms
:- data dbconn_desc/6.                       % Describes DBs management itf
:- data search_expire/1.                     % Searches' date of caducity
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Set of static persistent predicates used by dbserver which are physically 
% stored in directory DBDESC.
% text_option/3                              Stores system to user messages
% search/3                                   Stores user search results
% dbtables_desc/3                            Stores table descriptions
% sql_location/2                             Stores remote SQL DBs locs
% table_stats/7                              Stores system statistics
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Auxiliary data predicates used in run time:

:- data dbtable/5.                   % ID, Description, Arity, FieldTypes, Site
                                     % Correlates to persistent predicate
                                     % dbtables_desc/3

:- data html_option/4.               % Name, Explanation, Params, HTML_code
                                     % Correlates to the system html templates
                                     % stored in DBTEMPLATES directory

:- data db_dir/1, db_url/1, db_types/1, db_css/1, db_templates/1. 
:- data db_os/1, db_location/1 . 
:- data reviewer_email/1.

                                     % Index the location of several resources 
                                     % of the system

:- data launch_date/1.               % date of dbserver last launch

:- dynamic sql_persistent_location/2.   % Description of remote SQL DBs.
                                     % Correlates to persistent predicate 
                                     % sql_location/2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- include(password).                % Defines fact password/1

%%%%%%%%%%%%%%%%%%%%%%%%%%% Basic doc types %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- comment(doinclude, passwd/1).
:- regtype passwd(X) # "@var{X} is an identifier for the client type.".

:- comment(doinclude, dic/1).
:- regtype dic(X) # "@var{X} is a Pillow dictionary.".

dic(_).

:- comment(doinclude, output_page/1).
:- regtype output_page(X) # "@var{X} is an HTML page.".

output_page(_).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%% System services implementation %%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- comment(usage,"This module is typically compiled as an
   @index{active module} (i.e., a @index{server}). Clients can access
   this server by using the active modules usage convention:
@begin{verbatim}
:- use_active_module(dbserver).
@end{verbatim}
").

:- pred db_handler(+Input, +Passwd, -Response) :: dic * passwd * output_page
	
   # "Used to provide an interface to the service requested by the generic 
      client. Once this predicate identifies the required service, it calls 
      the particular predicate that implements it. ".

db_handler(Input, Passwd, Response):-
        log_message(called(db_handler(Input, Passwd))),
	get_form_value(Input, service, Value),
	call_service(Value, Input, Passwd, Response).

:- pred call_service(+Service, +Input, +Passwd, -Response) 

   #  "Calls the required service @var{Service} and obtains its results in 
       @var{Response}. The other input parameters are used by the predicate 
       that implements the service required. ".

call_service(system_status_service,_,_,Response):- !,
	status(Response).
call_service(db_connect_service, Input, Passwd, Response):- !,
	connect_to_DB(Input, Passwd, Response).
call_service(select_query_modify_service, _Input, Passwd, Response):- !,
	get_relationsdb(Passwd, Response).
call_service(query_modify_service, Input, Passwd, Response):- !,
	consultdb(Input, Passwd, Response).
call_service(complex_query_service, Input, Passwd, Response):- !,
	complex_query(Input, Passwd, Response).
call_service(modify_options_service, Input, Passwd, Response):- !,
	consultdboptions(Input, Passwd, Response).
call_service(create_table_service, Input, Passwd, Response):- !,
	create_destroy_table(Input,Passwd, Response).
call_service(select_table_to_edit_service, _Input, Passwd, Response):- !,
	select_table_to_edit(Passwd, Response).
call_service(table_to_edit_service, Input, Passwd, Response):- !,
	edit_table(Input, Passwd, Response).
call_service(reload_templates_service, _Input, Passwd, Response):- !,
	updatetemplates(Passwd, Response).
call_service(show_package, Input, _Passwd, Response):- !,
	show_package(Input, Response).
call_service(send_to_author, Input, _Passwd, Response):- !,
	send_to_author(Input, Response).
call_service(delete_package, Input, _Passwd, Response):- !,
	delete_package(Input, Response).

:-pred db_maintenance(+Input, +Passwd, -Response) :: dic * passwd * output_page

   #  "According to the kind of user that calls this predicate, the system 
       generates an interface which provides access to the whole of its 
       services (in the administrator case) or to a restricted set of them 
       (in the case of a common user). Typically used in installation time to
       automatically generate both common user and administrator front pages.".

db_maintenance(Input, Passwd, Response):-
        log_message(received(db_maintenance)),
	get_form_value(Input, maintenance, yes),!,
	db_handler(Input, Passwd, Response).
	
db_maintenance(_Input, Passwd, Response):-	
	html_option(common_footer,_, [], Footer),
	html_option(main_page_heading,_, [Heading], Head),
	html_option(page_body,_, [[Head, Content, Footer]], Body),
	html_option(admin_content,_, [Links], Content),
	html_option(ok_button,_,[],Ok_Button),
        log_message('finished with html options'),
	set_actions(service, Passwd, none, "tckxplyv", Services),
% If variable Passwd is not ground the user is considered to be a common one
	(var(Passwd) ->
	 platform_cgi('webdb_front', CGI),
%	 CGI='webdb_front.cgi',
	 text_option(webdb_heading,_, Heading),
         text_option(webdb_title,_, Title),
         log_message('finished with text options (1)')
	;
%	 CGI='admin/db_maintenance.cgi',
	 platform_cgi('admin/db_maintenance', CGI),
	 text_option(maintenance_title,_, Title),
	 text_option(maintenance_heading,_, Heading),
         log_message('finished with text options (2)')
 ),
        Links = [start_form(CGI),
	         Services,
		 Ok_Button,
		 input(hidden, [name=maintenance, value=yes]),
		 end_form],
        log_message('replying full page'),
        full_reply_page(Title, Body, Response).

:- data 'Software_Description_Request'/28.
:- data 'Dictionary'/3.

:- pred amos_reviewer_access(+Input, +Passwd, -Response) :: dic * passwd * 
	output_page
   # "Specialized service for AMOS. It implements a front end for AMOS 
      reviewers in order to provide a means to have a clear and fast view of 
      the work to do. ".

amos_reviewer_access(Passwd, Response) :-
        check_passwd(Passwd),
        html_option(common_footer,_, [], Footer),
        html_option(main_page_heading,_, [Heading], Head),
	html_option(page_body,_, [[Head, Content, Footer]], Body),
        html_option(admin_content,_, [Form], Content),
	db_url(URL),
%	text_option(pending_message,_,Pending),
 	findall([Link, Author, SubmittedOn],
 	        ('Software_Description_Request'(Id, PackageName,_,_,_,_,_,
		              Author,_,_,_,_,_,_,_,_,_,_,_,_,_,SubmittedOn,
		              _,_,_,_,_,_),
		 build_link(Id, URL, PackageName, Link)
		),
 		Pendings),
	amos_reviewer_access_heading(H),
	get_heading_straight(H, HE),
%	make_row(HE, FirstRow),
	html_option(table_row2, _, [HE], FirstRow),
	get_general_rows(Pendings, table_row2, RowPendings),
	Rows = [FirstRow|RowPendings],
	html_option(general_table,_,['3', Rows], Links),
	text_option(review_title,_, Title),
	text_option(review_heading,_, Heading),
	Form = [start_form,
	        Links,
		end_form],
        full_reply_page(Title, Body, Response).
	
amos_reviewer_access_heading(Heading) :-
	Heading = ['Package name', 'Author', 'Submission date'].

build_link(Id, URL, PackageName, Href) :-
	atom_concat([URL, 'admin/db_generic_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', Id, '&service=query_modify_service&action=amos_search'], Link),
	Href = a([href=Link], PackageName).

:- pred connect_to_DB(+Input, +Passwd, -Response) :: dic * passwd * output_page

   # "Implements remote SQL database connection management service. 
      Provides an interface used to connect to a given remote SQL database 
      through ODBC given a user password and an IP address. It can be used to
      unlink the system from a previously connected database too. ".

connect_to_DB(Input, Passwd, Response):-
	check_passwd(Passwd),
	get_form_all_vals(1, 6, dbconn_desc, Input, LFields, LValues),
	operate_on_dbs(Input, LValues, Reply),
	html_option(admin_footer, _, [], Footer),
	text_option(db_connection_title,_, Title),
	text_option(db_connection_heading,_,Heading),
	html_option(page_body,_, [[Head, Content, Footer]], Body),
	html_option(admin_heading,_, [Heading], Head),
	html_option(admin_content,_, [DBManagement], Content),        
	html_option(ok_button,_,[],Ok_Button),
	html_option(reset_button,_,[],Reset_Button),
	html_option(form_buttons,_,[Ok_Button, Reset_Button], Buttons),
	text_option(add_database_message, _, AText),
	html_option(general_add, _, [AText], Add_db_message),
	dbconn_desc(FD1,FD2,FD3,FD4,FD5,FD6),
	input_field_(LFields, [FD1,FD2,FD3,FD4,FD5,FD6], Passwd, InputFields),
	html_option(general_table,_,['2',InputFields], Form),
	findall(Site, sql_persistent_location(Site, _), SitesList), 
	(SitesList=[] ->
	 Del_db_message=[],
	 Del_Menu=[],
	 Message=[],
	 Current_DBs=[]	
	;
	 findall(d(DBId, DBHandle, User, IP_Add, Port),	
	         sql_persistent_location(DBId,db(DBHandle,User,_,IP_Add:Port)),
		  DBList),
	 text_option(del_database_message, _, DDText),
	 html_option(big_bold_message, _, [DDText], Del_db_message),
	 db_options(SitesList, Options),
	 html_option(db_connection_message,_,[],Message),
	 get_descriptions([FD1,FD2,FD3,FD5,FD6], Descs),
	 get_general_heading(Descs, DBTheading),
	 get_general_rows(DBList, table_row, DBRows),
	 make_row(DBTheading, Row1),
	 Rows=[Row1|DBRows],
	 html_option(general_table,_, ['5', Rows], Current_DBs),
	 Del_Menu=select([name=dest_list], [option([value=not_destroy], 'Keep current links')|Options])),
	 platform_cgi('admin/db_generic_client', CGI),
	 DBManagement=[
			  Reply,
			  Message,
%                        start_form('admin/db_generic_client.cgi'),
			  start_form(CGI),
			  Current_DBs,
			  Del_db_message,
			  Del_Menu,
			  Add_db_message,
			  Form,
			  Buttons,
			  input(hidden, [name=selected, value=yes]),
			  input(hidden, [name=service,value=db_connect_service]),
			  end_form
                     ],
        full_reply_page(Title, Body, Response).	

:- pred get_descriptions(+FieldTypes, -FieldDescs)

   # " For a given list @var{FieldTypes} of html types in dbtables_desc/3 
       format obtains a list @var{FieldDescs} that contains the descriptive 
       names of any. ".

get_descriptions([], []).
get_descriptions([FT1|FTs],[Desc1|Descs]):-
	type_info(FT1, _, Desc1),
	get_descriptions(FTs, Descs).

:- pred operate_on_dbs(+Input, +Lvalues, -Reply)

   # " Generates a form that takes from the user the information the system 
       needs to connect to a given remote SQL database or unlink it from an 
       already linked one. ".

operate_on_dbs(Input, LValues, Reply):-
	get_form_value(Input, selected, yes), !,
	get_form_value(Input,dest_list,DB2Dest),
	unlink_DB(DB2Dest),
	turn2terms(LValues, LTerms),
	DBLinks_pred=..[c_form|LTerms],
	(ground(DBLinks_pred)->
	 turn2loc_syntax(DBLinks_pred, Loc),
	 link_DB(Loc, Add_Reply)
	;
	 text_option(insert_warning, _, Message),
	 html_option(consult_table_message,_, [Message], Add_Reply)
	),
	(DB2Dest=not_destroy ->
	 Reply=Add_Reply
	;
	 Reply=[]
	).
operate_on_dbs(_,_,[]).

:- pred turn2loc_syntax(+InFormat, -OutFormat)

   # "Turns some data into the @pred{sql_persistent_location/2} particular
     format. ".    

turn2loc_syntax(c_form(DbId, DbH, U, Pass, IP, Port), 
	sql_persistent_location(DbId, db(DbH, U, Pass, IP:Port))).
	
:- pred unlink_DB(+Link2Destroy)
   
   # "When the user has selected which remote SQL Database is to be unlinked 
      from the system, carries out the particular protocol necessary to do it 
      cleanly. ".

unlink_DB(not_destroy):- !.
unlink_DB(DbId):-
	reset_stats(DbId),
	pretract_all(dbtables_desc(_, DbId,_)),
	pretract_all(sql_location(DbId,_)),
	retractall(dbtable(_,_,_,DbId,_)),
	retractall(sql_persistent_location(DbId,_)).

:- pred link_DB(+Database2Link, -Reply)

   # "Tests if the remote SQL database is already linked from the same source. 
      If not, links it to he system. ".

link_DB(NewLoc, Reply):-
	call(NewLoc), !,
	text_option(repeated_elem, _, Message),
        html_option(consult_table_message,_, [Message], Reply).
link_DB(sql_persistent_location(DbId, db(DbH, U, _, _:_)), Reply):-
	(current_fact(sql_persistent_location(DbId, db(_, U, _, _:_)));
	 current_fact(sql_persistent_location(_, db(DbH, U, _, _:_)))), !,
	text_option(repeated_Id, _, Message),
        html_option(consult_table_message,_, [Message], Reply).
link_DB(sql_persistent_location(DbId, DBHandle), []):-
	data_facts:assertz_fact(sql_persistent_location(DbId, DBHandle)),
	persdbrt:assertz_fact(sql_location(DbId, DBHandle)),
	sql_get_tables(DbId, TablesList),
	include_tables(DbId, TablesList).

:- pred include_tables(+DatabaseId, -Reply)

   # "Includes the tables of a remote SQL database into the system's database 
      tables description. ".

include_tables(_,[]).
include_tables(DbId,[T1|Ts]):-
	sql_table_types(DbId, T1, Double_list), 
	obtain_ids_types(Double_list, A_list, T_list),	
	SQLDef=..[T1|A_list],
	PrologDef=..[T1|T_list],
	make_sql_persistent(PrologDef, SQLDef, DbId),
	attach2DB(DbId, T1, A_list, T_list),
	include_tables(DbId,Ts).

:- pred obtain_ids_types(+Pairs, -Ids, -Types)

   # "Transforms a list of pairs (FieldIdentifier, FieldSQLtype) in a list of 
      FieldIdentifier and a list of FieldSQLtype." .

obtain_ids_types([],[],[]).
obtain_ids_types([[Id, Type]|Rest], [Id|Ids], [Type|Ts]):-
	obtain_ids_types(Rest, Ids, Ts).

:- pred attach2DB(+DbId, +T1, +A_list, +T_list)

   # "Given a remote SQL database identifier @var{DbId}, a table name of that
      database @var{T1}, its attributes identifier list @var{A_list} and its
      attributes type list @var{T_lists}, attaches that particular table to 
      the system. ".

attach2DB(DbId, T1, A_list, T_list):-
	get_html_args(A_list, T_list, Html_args),
	NewTableDesc=..[T1|Html_args],
	date(Date),	
	persdbrt:assertz_fact(dbtables_desc(NewTableDesc, DbId, Date)),
	atom_concat([T1,'_',DbId], TName),
	length(A_list, A),
	data_facts:assertz_fact(dbtable(TName, A, NewTableDesc, DbId, Date)),
	initialize_stat(TName).
	
:- pred get_html_args(+A_list, +T_list, -Html_args_list)

   # " Given a list of attributes @var{A_list} and a correlated list of their 
       SQL types @var{T_list}, obtains a list of @pred{dbtables_desc/3} syntax
       html attributes. ".

get_html_args([], [], []).
get_html_args([A|A_list], [T|T_list], [H|Html_args]):-
	sql2html_aux(T, A, H),
	get_html_args(A_list, T_list, Html_args).

:- pred sql2html_aux(+SQLtype, +AttributeId, +HTMLarg)

   # "Given an SQL type @var{SQLtype} and an attribute identifier 
      @var{AttributeId}, obtains a @pred{dbtables_desc/3} syntax
       html attribute @var{HTMLarg}. Default is a name type".

sql2html_aux(T, A, H):-
	sql2html(T, A, H), !.
sql2html_aux(_, A, name(A,'',20)).

:- comment(get_relationsdb/2,"Implements with @pred{consultdb/3} the query 
      one tuple service. It displays an HTML form with a menu where
      the user can choose the table to operate on. It makes out wether
      the user is an authorised client or a common one by checking the argument
      @var{ClientType}. If it's an authorised client, the user will be
      allowed to insert, delete or modify data, otherwise just consult
      the system to make standard search operations.").

:- pred get_relationsdb(+ClientType, -OutputFields) :: passwd * output_page

   # "@var{OutputFields} is a form where the user can choose a
      relation to operate on (the operations permitted depend on
      @var{ClientType}).".

get_relationsdb(ClientType, Reply):-
       get_opts_clienttype(ClientType, CGI, Footer),
       text_option(choose_table_title,_, Title),
       text_option(choose_table_heading,_,Heading),
       html_option(page_body,_, [[Head, Content, Footer]], Body),
       html_option(admin_heading,_, [Heading], Head),
       html_option(admin_content,_, [Choose_table], Content),
       text_option(choose_table_message,_,CTMText),
       html_option(general_choose,_,[CTMText],Message),
       html_option(ok_button,_,[],Ok_Button),
       (var(ClientType) -> 
	 findall(TI, (dbtable(TI, _, _, _,_), 
	        \+(TI='Software_Description_Validation'),
		\+(TI='Administration'),
	        \+(TI='Dictionary')), TIList)
	;
	 findall(TI, dbtable(TI, _, _, _,_), TIList)),
        table_options(TIList, Options),
        Choose_table = [Message,
                        start_form(CGI),
                        select([name=table_name], Options),
                        input(hidden,[name=action, value=empty_form]),
			   input(hidden,[name=searchmode, value=normal]),
			   input(hidden,[name=service,value=query_modify_service]),
                        \\, \\,
                        Ok_Button,
                        end_form
                       ],
       full_reply_page(Title, Body, Reply).

:- pred get_opts_clienttype(+Password, -CGI, -Footer) 

   # "Sets the CGI client to interact with the user depending on the password 
      produced. If no password is given, a simple CGI client with no 
      privilegies is prompted. In other case, if the password is right the CGI 
      will be one with full access to the system services, otherwise the 
      system acts against the aggression. ".

get_opts_clienttype(V, CGI, Footer) :-
       var(V), !,
	platform_cgi('webdb_client', CGI),
	html_option(common_footer, _, [], Footer).
get_opts_clienttype(Passwd, CGI, Footer) :-
       password(Passwd), !,
	platform_cgi('admin/db_generic_client', CGI),
	html_option(admin_footer, _, [], Footer).
get_opts_clienttype(Bad, _, _) :-
       bad_passwd(Bad).

:- pred table_options(+TablesList, -Menu)
	
   # "Given a list of tables @var{TableList}, generates a Pillow menu in order
      to allow the user to choose one @var{Menu}. ".

table_options([], []).
table_options([TI|TIList], [Opt|OptList]) :-
       current_fact(dbtable(TI, _, _, _, _)),
       Opt = option([value=TI], TI),
       table_options(TIList, OptList).

:- pred db_options(+DatabaseList, -Menu)
	
   # "Given a list of databases @var{DatabaseList}, generates a Pillow menu 
      in order to allow the user to choose one @var{Menu}. ".

db_options([], []).
db_options([Site|SiteList], [Opt|OptList]) :-
       current_fact(dbtable(_, _, _, Site,_)),
       Opt = option([value=Site], Site),
       db_options(SiteList, OptList).


set_empty_or_not([], [], []) .
set_empty_or_not([V|Vs], [GV|GVs], [NV|NVs]) :-
	(var(V) ->
	 (GV = '' ->
	  NV = '$empty'
	 ;
	  NV = GV)
	;
	 NV = V),
	set_empty_or_not(Vs, GVs, NVs) .

:- comment(consultdb/3,"Once the user has decided on which table to
   operate on through the service provided by
   @pred{get_relationsdb/2}, @pred{consultdb/3} displays the system
   consult interface. Through this interface the user can choose which
   operation to carry out over the database.").

:- pred consultdb(+Input, +ClientType, -Response) :: dic * passwd * output_page

   # "Consults @var{Input} and returns @var{Response} as a response
      for a given client type @var{ClientType}.".

consultdb(Input, ClientType, Response) :-
	get_form_value(Input, table_name, Table), !,
	dbtable(Table, Arity, TableFieldsDesc,_,_),
	TableFieldsDesc=..[_|FD],
	Arity > 0, !,
	get_form_value(Input, action, Action), 
	(Action=refresh ->
	 get_form_value(Input, search, Session),
	 make_refresh(Session, Input, Table, Arity, ClientType, Next, Reply, Data)
	;
	 get_form_all_vals_typed(1, Arity, Table, Input, FD, LFields, LValues),
	 get_form_value(Input, 'Software_Description_Request_1', Id),
	 (\+(Id ='') ->
	  get_field_names(1, Table, Arity, LFields),
	  functor(Term, Table, Arity),
	  Term =.. [Table,_|LValuesAuxR],
	  LValues = [_|LValuesR],
	  set_empty_or_not(LValuesAuxR, LValuesR, LValuesAmos),
	  full_reply(Action, Input,  ClientType, Table, LFields, 
	            [_|LValuesAmos], Next, Reply, Data, Success)
	 ;
	  full_reply(Action, Input,  ClientType, Table, LFields, 
	            LValues, Next, Reply, Data, Success))),
	((Action=overview; (Action=full_search, var(ClientType))), Success=yes ->
	  Buttons=[],
	  Select_action=[],
	  Select_Search_Mode=[],
	  Search_Mode=[]
	 ;
%	  (Next = [0'i] -> 
	  (transparent_insert(ClientType, Table, Next) ->
	   html_option(ok_button_first,_, [], Ok_Button),
	   html_option(reset_button_first,_, [], Reset_Button)
         ;
	   html_option(ok_button,_, [], Ok_Button),
	   html_option(reset_button,_, [], Reset_Button)
         ),
	  html_option(form_buttons,_, [Ok_Button, Reset_Button], Buttons),
	  (((Action\==full_search, Action\==overview); Success=no) ->
	   text_option(select_action_message, _, SAText),
	   html_option(general_sel_mess,_, [SAText],Select_action),
	   text_option(select_search_mode_mess, _, SSMText),
	   html_option(general_sel_mess,_, [SSMText],Select_Search_Mode),
	   get_form_value(Input, searchmode, SearchAux),
	   (SearchAux = '' ->
	    Search = normal
	   ;
	    Search = SearchAux),
	   set_search_mode(Search, "ncr", Search_Mode)
	  ;
	   Select_action=[],
	   Select_Search_Mode=[],
	   Search_Mode=[]
	  )
	),
	(Table = 'Dictionary' ->
	 (select(0'f, Next, Next1); Next = Next1)
	;
	 Next = Next1),
	set_actions(action, ClientType, Table, Next1, NextActions),
       set_footer_CGI(ClientType, Footer, CGI),
       text_option(database_consult_title,_, Title),
	(Table = 'Software_Description_Request' ->
	 (var(ClientType) ->
	  get_form_value(Input, s_comment, Submited_Suggestions),
	  ((form_empty_value(Submited_Suggestions); Submited_Suggestions=[])->
	   Rec_sug = []
	  ;
	   !, persdbrt:assertz_fact(suggestions(Submited_Suggestions)),
	   text_option(suggestion_received,_, Rec_sug_text),
	   html_option(consult_table_message,_, [Rec_sug_text], Rec_sug)
	  ),
	  text_option(s_d_r_heading1, _, TableH),
	  text_option(amos_add_package_readme_text, _, ReadmeText),
	  text_option(user_suggestions, _, SuggestionsText),
	  html_option(suggestions,_,[SuggestionsText,s_comment],Suggestions)
	 ;
	  text_option(s_d_r_heading2, _, TableH),
	  ReadmeText = [],
	  Suggestions = [],
	  Rec_sug = []
	 )
	;
	 ReadmeText = [],
	 Suggestions = [],
	 Rec_sug = [],
	 (Table = 'Software_Description_Validation' ->
	  text_option(s_d_v_heading, _, TableH)
	 ; (Table = 'Administration', var(ClientType)) ->
	   text_option(a_heading,_,TableH)
	 ;
	  TableH = Table)
	),
       html_option(consult_table_heading,_, [TableH, ReadmeText], Head),
       html_option(page_body,_, [[Head, Content, Footer]], Body),
       html_option(admin_content,_, [ConsultDB], Content),
	((var(ClientType), Table \== 'Administration') ->
	 Actual_Search_Mode=input(hidden,[name=searchmode, value=case_ins]),
	 Actual_Select_Search_Mode = []
	;
	 Actual_Search_Mode = Search_Mode,
	 Actual_Select_Search_Mode = Select_Search_Mode
	),
	(transparent_insert(ClientType, Table, Next) ->
	 Sam = input(hidden, [name=action, value=insert])
	; transparent_ov(ClientType, Table, Next) ->
	  html_option(control_row,_,[Actual_Select_Search_Mode, []], Row1),
	  html_option(control_row,_,[Actual_Search_Mode, []], Row2),
	  html_option(no_frame_table,_, ['2', [Row1, Row2]], Sam1),
	  Sam = [input(hidden, [name=action, value=overview])|Sam1]
	;
	  html_option(control_row,_,[Select_action, Actual_Select_Search_Mode], Row1),
	  html_option(control_row,_,[NextActions, Actual_Search_Mode], Row2),
	  html_option(no_frame_table,_, ['2', [Row1, Row2]], Sam)
	),
	ConsultDB = [Reply,
	             Rec_sug,
		      start_form(CGI),
		      Data,
		      Sam,
		      input(hidden,[name=table_name, value=Table]),
		      input(hidden,[name=service,value=query_modify_service]),
		      Suggestions,
		      Buttons,
		      end_form
                    ],
        full_reply_page(Title, Body, Response).
consultdb(_, _, Response):-
	text_option(database_consult_title,_, Title),
	text_option(table_not_ready_mess, _, Message),
	short_response(Title, Message, Response).	

:- pred get_form_all_vals(+N, +Arity, +TI, +Input, -FieldList, -FieldValsList)

   # "Given a table @var{TI}, its arity @var{A} and a Pillow Input @var{Input}
      , obtains from a form the fields and their values associated. ".

get_form_all_vals(N, Arity, _, _, [], []) :- N > Arity, !.
get_form_all_vals(N, Arity, TI, Input, [F|Fs], [FV|FVs]):-
	number_codes(N, S),
	atom_codes(U, "_"||S),
	atom_concat(TI, U, F),
	get_form_value(Input, F, FV),
	N1 is N+1,
	get_form_all_vals(N1, Arity, TI, Input, Fs, FVs).	

get_form_all_vals_typed(N, Arity, _, _, [], [], []) :- N > Arity, !.
get_form_all_vals_typed(N, Arity, TI, Input, [D|Ds], [F|Fs], [FV|FVs]):-
	number_codes(N, S),
	atom_codes(U, "_"||S),
	atom_concat(TI, U, F),
	functor(D, Func, _),
	(multiple(Func) ->
	 arg(4, D, M),
	 get_values_from_multiple_menu(Input, F, 1, M, FV)
	;
         get_form_value(Input, F, FV)
	),
	N1 is N + 1,
	get_form_all_vals_typed(N1, Arity, TI, Input, Ds, Fs, FVs).	

multiple(multiple_menu_ck).
multiple(multiple_menu_rq_ck).

get_values_from_multiple_menu(Info, F, I, M, ValuesList) :-
	I =< M, !,
	atom_number(AI, I),
	atom_concat([F, '_menu_', AI], Field),
	findall(Value, member(Field = Value, Info), Values),
	append(Values, RVs, ValuesList),
	I1 is I + 1,
	get_values_from_multiple_menu(Info, F, I1, M, RVs).
get_values_from_multiple_menu(_Info, _F, _I, _M, []).

:- pred get_field_names(+N, +TI, +Arity, -FieldList)

   # "Given a table @var{TI} and its arity @var{A}, obtains the field names 
      for the form associated. ".

get_field_names(N, _, Arity, []):- N > Arity, !.
get_field_names(N, T, Arity, [F|Fs]):-
	number_codes(N, S),
        atom_codes(U, "_"||S),
        atom_concat(T, U, F),
	N1 is N + 1,
	get_field_names(N1, T, Arity, Fs).


amos_entity('Dictionary').

get_packages(Name,Pred):-
	Pred = 'Administration'(Key, Name, Desc, Author),
	get_pkg(Key0, Name0, Desc0, Author0),
	format_args([Key0, Name0, Desc0, Author0],[Key, Name, Desc, Author]).

get_administrations([],[]).
get_administrations([_-Adm|Pairs],[Adm|Adms]):-
	get_administrations(Pairs,Adms).

:- pred prepare_search(+Search_Mode, +Table, +LValues, -LPreds)

   # "Obtain the matches of a query in a table @var{Table} given the 
      search mode @var{Search_Mode} and the input values @var{LValues}. 
      Basically, transforms the input form fields and values into a prolog 
      term and finds all the terms contained in the table that match it. ".

prepare_search(Search_Mode, 'Administration', LValues, LPreds):- !,
	get_single_query('Administration', LValues, Pred0),
	(Search_Mode = normal ->
	 Pred = Pred0
	;
	 Pred = 'Administration'(_,_,_,_)
	),
	setof(Name-Pred, get_packages(Name,Pred), LPreds00),
	get_administrations(LPreds00, LPreds0),
	(Search_Mode = normal ->
	 LPreds = LPreds0
	;
	 writeq(make_filter(Search_Mode, Pred0, LPreds0, LPreds)),nl,
	 make_filter(Search_Mode, Pred0, LPreds0, LPreds),
	 writeq(make_filter(Search_Mode, Pred0, LPreds0, LPreds)),nl).
prepare_search(normal, Table, LValues0, LPreds):- 
	amos_entity(Table),
	build_ditem(LValues0, var, DItem, Entry, Errs), !,
	((Errs = []; var(Entry)) ->
	 findall(DItem, ditem_description(DItem,_), LDItems),
	 ditems2terms(LDItems, Table, LPreds0),
	 LPreds = LPreds0
	;
	 LPreds= []).
prepare_search(normal, Table, LValues, LPreds):- !,
	current_fact(dbtable(Table, _, _, SiteId,_)),
	get_single_query(Table, LValues, Pred),
	itf_findall(SiteId, Pred, Pred, LPreds).
prepare_search(Search_Mode, Table, LValues, LPreds):- 
	amos_entity(Table), !, 
	arrange_free_search(Table, LValues, Pred, VarList,_),
	build_ditem(VarList, var, FreeDItem,_,_),
	findall(FreeDItem, ditem_description(FreeDItem,_), DItems),
	ditems2terms(DItems, Table, LPreds0),
	make_filter(Search_Mode, Pred, LPreds0, LPreds).
prepare_search(Search_Mode,Table, LValues, LPreds):-
	arrange_free_search(Table, LValues, Pred, VarList, SiteId),
	(SiteId = local ->
	 PredAux=..[Table|VarList]
	;
	 get_remote_table_name(SiteId, Table, TName),
	 PredAux=..[TName|VarList]),
	itf_findall(SiteId, PredAux, PredAux, LPredsAux),
	make_filter(Search_Mode, Pred, LPredsAux, LPreds).

arrange_free_search(Table, LValues, Pred, VarList, SiteId) :-
	get_single_query(Table, LValues, Pred),
	current_fact(dbtable(Table, Arity, _, SiteId, _)),
	get_free_vars(1, Arity, VarList).

:- pred make_filter(+Search_Mode, +Pred, +LPredsIn, -LPredsOut)

   # "Given a search criterium @var{Search_Mode}, a pattern @var{Pred}, and a
      list of candidates for match @var{LPredsIn} obtains a list with do match
      according to that criterium. ".

make_filter(case_ins, Pred, LPredsAux, LPreds):-
	!, filter_cimatch(Pred, LPredsAux, LPreds).
make_filter(reg_exp, Pred, LPredsAux, LPreds):-
	filter_regmatch(Pred, LPredsAux, LPreds).

:- pred filter_cimatch(+Pred, +LPredsIn, -LPredsOut)

   # "Given the search criterium case_insensitive_match, a pattern @var{Pred}, 
      and a list of candidates for match @var{LPredsIn}, obtains a list with 
      do match according to that criterium. ".

filter_cimatch(_, [], []).
filter_cimatch(Pred, [PredAux1|PAs], [PredAux1|Ps]):-
	case_insensitive_match_0(Pred, PredAux1), !,
	filter_cimatch(Pred, PAs, Ps).
filter_cimatch(Pred, [_PredAux1|PAs], Ps):-
	filter_cimatch(Pred, PAs, Ps).

case_insensitive_match_0(Pred0, PredAux0) :-
	no_list(Pred0, Pred),
	no_list(PredAux0, PredAux),
	case_insensitive_match(Pred, PredAux).

:- pred filter_regmatch(+Pred, +LPredsIn, -LPredsOut)

   # "Given the search criterium regular_expresion_match, a pattern @var{Pred},
      and a list of candidates for match @var{LPredsIn}, obtains a list with 
      do match according to that criterium. ".

filter_regmatch(_, [], []).
filter_regmatch(Pred, [PredAux1|PAs], [PredAux1|Ps]):-
	match_pattern_pred_0(Pred, PredAux1), !, 
	filter_regmatch(Pred, PAs, Ps).
filter_regmatch(Pred, [_PredAux1|PAs], Ps):-
	filter_regmatch(Pred, PAs, Ps).

match_pattern_pred_0(Pred0, PredAux0) :-
	no_list(Pred0, Pred),
	no_list(PredAux0, PredAux),
	match_pattern_pred(Pred, PredAux).

:- pred register_search(+LPreds, -Session)

   # "Registers the list of terms resulting from a query @var{LPreds}, and 
      produces a session number @var{Session} associated to it. ".

register_search(LPreds, Session):-
	enumerate(LPreds, LNumbPreds, Length),
	time(Session),
	clear_old_sessions(Session),
	persdbrt:assertz_fact(search(Session, LNumbPreds, Length)). 

:- pred set_search_mode(+Action, +Actions, -Search_Mode)

   # "Produces a checkpoint list @var{Search_Mode} where the user can choose 
      the kind of match desired for a particular one table query. ".

set_search_mode(Action, Actions, Search_Mode):-
	Actions=[A|_],
	char_to_search_mode(A, Default),
	compute_search_mode(Action, Actions, Default, Search_Mode).

compute_search_mode(_, [], _, []).
compute_search_mode(Action, [A|As], Def, [S1|Ss]):-
	char_to_search_mode(A, S_mode),
	compute_checked(Action, S_mode, Checked),
	text_option(S_mode, _, Text),
	html_option(search_mode_display,_,[S_mode,Text,Checked], S1),
	compute_search_mode(Action, As, Def, Ss).

char_to_search_mode(0'n, normal).
char_to_search_mode(0'c, case_ins).
char_to_search_mode(0'r, reg_exp).

no_special(Table) :-
	\+ Table = 'Software_Description_Request',
	\+ Table = 'Software_Description_Validation'.

:- pred full_reply(Action, Input, Table, LFields, LValues, 
	Mode, Reply, Data, Success) 

   # "For a given table @var{Table} and fields @var{LFields} and
      values @var{LValues} of a form it gives the accurate reply
      @var{Reply}, provides a new form data @var{Data} and a new mode
      @var{Mode} for the following actions.".

% Starting a search from scratch.
full_reply(empty_form,_Input,CType,Table,LFields,_LValues,Act,[],Form,_):-
% 	(var(CType) ->
% 	 Act = "i"
% 	;
	 (Table = 'Administration' ->
	  Act = "o"
	 ;
	  (var(CType) ->
	   Act = "i"
	  ;
	   Act= "sofid")),
%	),
	input_field(Table, LFields, CType, Form).
% Typical search action
full_reply(search,Input,CType,Table,LFields,LValues,Actions,Reply,Form,yes):-
	get_form_value(Input, searchmode, Search_Mode),
%	display(prepare_search(Search_Mode, Table, LValues, LPreds)), nl,
	prepare_search(Search_Mode, Table, LValues, LPreds),
%	display(prepare_search_done), nl, 
	LPreds \== [], !,
	register_search(LPreds, Session),
	search_vals(Session, 1, Table, LFields, CType, Actions, Reply, Form),
	update_stats(Table, s).
full_reply(search,_Input,CType,Table,LFields,_LValues,"sofid",Reply,Form,no):-
        text_option(no_found_reply, _, Message),
        html_option(consult_table_message,_, [Message], Reply),
	input_field(Table, LFields, CType, Form).
% AMOS search
full_reply(amos_search,Input, CType, Table, LFields, LValues, Actions, Reply, Form,yes):-
        (Table = 'Software_Description_Request' ->
         get_form_value(Input, 'Software_Description_Request_1', Id),
	 atom_number_if_needed(AId, Id),
         LValues=[AId|_]
        ;
         true
        ),
	get_form_value(Input, validated, Validated),
	(Validated = yes ->
	 db_to_plain(AId, LPreds) %Explore the main DB and retrieve a list of result preds
	;
         prepare_search(normal, Table, LValues, LPreds)),
        LPreds \== [], !,
        register_search(LPreds, Session),
        search_vals(Session, 1, Table, LFields, CType, Acts, Reply, Form),
        (var(CType) ->
	 (Validated = yes ->
	  Actions = "i"
	 ;
	  Actions = "mdi"
	 )
        ;
         Actions = Acts),
        update_stats(Table, s).
full_reply(amos_search,_Input,CType,Table,LFields,_LValues,"i",Reply,Form,no):-
       text_option(no_found_reply, _, Message),
       html_option(consult_table_message,_, [Message], Reply),
       input_field(Table, LFields, CType, Form).
% Show all matching tuples in a table (low level) search action
full_reply(overview, Input, CType, Table, _LFields, LValues, [], Reply, Form, yes):-
	get_form_value(Input, searchmode, Search_Mode),
	prepare_search(Search_Mode, Table, LValues, LPreds),
	LPreds \== [], !,
	register_search(LPreds, Session),
	overview_vals(Session, Table, CType, Reply, Form),
	update_stats(Table, s).	
full_reply(overview,_Input, CType,Table, LFields,_LValues, Actions, Reply, Form, no):-
	(Table = 'Administration' -> 
	 Actions = "o"
	;
	 Actions = "sofid"),
	text_option(no_found_reply, _, Message),
	html_option(consult_table_message,_, [Message], Reply),
	input_field(Table, LFields, CType, Form).
% Show all matching tuples in a table search action
full_reply(full_search, Input, CType,Table, _LFields, LValues, Actions, Reply, OutputForm, yes):-
	get_form_value(Input, searchmode, Search_Mode),
	prepare_search(Search_Mode, Table, LValues, LPreds),
	LPreds \== [], !,
	register_search(LPreds, Session),
	full_search(Table, Session, CType, Reply, Form),
	OutputForm=[input(hidden, [name=searchmode, value=normal])|Form],
	(var(CType) ->
	 Actions=[]
	;
	 Actions="r"),
	update_stats(Table, s).
full_reply(full_search,_Input, CType,Table, LFields,_LValues, "sofid", Reply, Form, no):-
       text_option(no_found_reply, _, Message),
       html_option(consult_table_message,_, [Message], Reply),
       input_field(Table, LFields, CType, Form).
% The user asks for the next element of a search
full_reply(ahead,Input,CType,Table, LFields,_LValues, Actions, Reply, Form,_):-
	get_form_value(Input, search, Session),
       get_form_value(Input, currelem, LastElem),
       Elem is LastElem+1,
       search_vals(Session, Elem, Table, LFields, CType, Actions, Reply, Form).
% The user asks for the previous element of a search
full_reply(back,Input, CType,Table, LFields,_LValues, Actions, Reply, Form,_):-
	get_form_value(Input, search, Session),
	get_form_value(Input, currelem, LastElem),
       Elem is LastElem-1,
       search_vals(Session, Elem, Table, LFields, CType, Actions, Reply, Form).
% Insertion of a new element of the relation
full_reply(insert,_Input,CType,Table,LFields,LValues,"esofidm",Reply,Form, _):-
	no_special(Table),
	dbtable(Table, _, Desc, _SiteId, _),
	Desc =..[_|FieldDescs],
	get_single_query(Table, LValues, Pred),
	output(Table, Pred, LFields, CType, Form),
	( \+ all_required(FieldDescs, LValues) ->
	   text_option(insert_warning, _, Message)
        ; (current_fact(Pred); 
	   (amos_entity(Table), ditem_current_fact_entry(LValues))) ->
	    text_option(repeated_elem, _, Message)
	 ; (amos_entity(Table) ->
	    build_ditem(LValues, null, DItem,_,_),!,
	    controlled_ditem_db_update(_, DItem, GFlag),!
	   ; 
	    itf_assert(Pred)
	   ), 
	   (var(CType) ->
	    text_option(element_inserted1, _, Message)
	   ;
	    text_option(element_inserted2, _, Message)
	   )
       ),
       generalization_warning(GFlag, M1),
       html_option(consult_table_message2,_, [M1, Message], Reply),
       update_stats(Table, i).
full_reply(insert,Input,CType,Table, LFields, LValues, Actions, Reply, Form, _):-
	dbtable(Table, _, Desc, _SiteId, _),
	get_single_query(Table, LValues, Pred),
	output(Table, Pred, LFields, CType, Form),
	(form_empty_list_values(LValues) ->
	 text_option(insert_warning, _, Message),
	 Actions = "sofiw"
	; current_fact(Pred) ->
	  text_option(repeated_elem, _, Message),
	  Actions = "sofiw"
	; 
	 (Table = 'Software_Description_Request' ->
	  get_form_value(Input, 'Software_Description_Request_1', Key),
	  ((Key = ''; form_empty_value(Key)) ->
	   create_key(R0),
	   atom_concat(R0, '_tmp', R)
	  ;
	   R = Key),
	  arg(1, Pred, R),
	  functor(TPack, Table, 28),
	  arg(2, Pred, PName),
	  arg(2, TPack, PName)
	 ;
	  Pred = TPack
	 ),
	 var_these(Pred, [22], NPred),
	 date(Date),
	 arg(22, NPred, Date),
	 Desc =..[_|FieldDescs],
	 NPred =..[_|Vals],
	 (\+ all_required(FieldDescs, Vals) ->
         text_option(insert_warning, _, Message),
	  Actions = "sofiw"
	 ;
	  current_fact(TPack) ->
	  text_option(repeated_package,_,Message),
	  Actions = "sofiw"
	 ;
	  vars_to_void(Vals, FilteredVals),
	  LValues = [_|RLValues],		   
	  (welcome_mail([R|RLValues]) ->
	   NNPred =..[Table|FilteredVals],
	   itf_assert(NNPred),
	   admin_mail([R|RLValues]),
	   Actions = "esofidw",
	   update_stats(Table, i),
	   (var(CType) ->
	    text_option(element_inserted1, _, Message)
	   ;
	    text_option(element_inserted2, _, Message)
	   )
	  ;
	   text_option(insert_error, _, Message))
	 )
       ),
       html_option(consult_table_message,_, [Message], Reply), !.
% Modification of a previously inserted element
full_reply(modify, Input, CType,Table, LFields, LValues, Actions, Reply, Form,_):-
	get_single_query(Table, LValues, NPred),
	get_form_value(Input, search, Session),
	current_fact(search(Session, LPreds, Length)),
	get_form_value(Input, currelem, Elem),
	select(pred(Elem, Pred), LPreds, LNewPreds),
	NPred =..[_|Vals],
	vars_to_void(Vals, NVals),
	NNPredAux =..[Table|NVals],
	dbtable(Table, _, Desc, _, _),
	Desc =..[_|FieldDescs],
	(Table = 'Software_Description_Request' ->
	 get_form_value(Input, 'Software_Description_Request_1', Id),
	 var_these(NNPredAux, [1], NNPred),	  
	 (number(Id) ->
	  atom_number(AId, Id)
	 ;
	  AId = Id
	 ),
	 arg(1, NNPred, AId)	  
	;
	 NNPred = NNPredAux
	),
	functor(TPack, Table, 28),
	arg(2, NPred, PName),
	arg(2, TPack, PName),
	(Table = 'Software_Description_Request', 
	 \+ all_required(FieldDescs, Vals) ->
	 text_option(ilegal_modify,_,Message),
	 output_search(Table, NPred, LFields, Session, Elem, CType, Form)
	;
         Table = 'Software_Description_Request', 
	 (current_fact(TPack), arg(1, TPack, TPI), \+ TPI=AId) ->
	 text_option(repeated_package,_,Message),
	 output_search(Table, NPred, LFields, Session, Elem, CType, Form)
	;
	 form_empty_list_values(LValues) ->
	 text_option(modify_warning,_, Message),
	 output_search(Table, Pred, LFields, Session, Elem, CType, Form)
	;
	 (current_fact(NNPred);
	  (amos_entity(Table), ditem_current_fact(LValues))) ->
	 text_option(repeated_elem,_, Message),
	 output_search(Table, NPred, LFields, Session, Elem, CType, Form) 
	;
	 persdbrt:retract_fact(search(Session, LPreds, Length)),
	 (amos_entity(Table) ->
	  build_ditem(LValues, null, DItem,_,_),!,
	  arg(1, Pred, Entry),
	  gen_ditem_list([Entry], DL,_),
	  DL = [NDI],
	  ditem_description(NDI, Key),
	  controlled_ditem_db_update(Key, DItem, GFlag),!
	 ;
	  itf_retract(TPack), !,
	  itf_assert(NNPred)
        ),
	 persdbrt:assertz_fact(search(Session, [pred(Elem, NNPred)|LNewPreds],
	  Length)),
	 text_option(element_modified,_, Message),
	 update_stats(Table, m),
	 output_search(Table, NNPred, LFields, Session, Elem, CType, Form)
	),
	generalization_warning(GFlag, M1),
	html_option(consult_table_message2,_, [M1, Message], Reply),
	(no_special(Table) ->
	 Actions = "esofidm"
	;
	 next_actions(Elem, Length, Actions)
	).
% Validation of a request
full_reply(validate, Input, CType,Table, LFields, LValues, Actions, Reply, Form,_):-
	LValues = [_|RLValues],
	LFields = [_|RLFields],
	dbtable('Software_Description_Validation', _, _, SiteId, _),
	get_single_query('Software_Description_Validation', [_|RLValues], NewPred),
	get_single_query(Table, LValues, OldPred),
        ( form_empty_list_values(LValues) ->
            text_option(insert_warning, _, Message)
        ; itf_call(SiteId, NewPred) ->
            text_option(repeated_elem, _, Message)
        ; 
	  get_form_value(Input, 'Software_Description_Request_1', R), 
	  (number(R) ->
	   atom_number(AR, R)
	  ;
	   AR = R),
	  arg(1, NewPred, AR),
	  ((\+ ground(NewPred), var(CType)) ->
            text_option(insert_warning, _, Message)
	  ;
	    verify(RLFields, RLValues, Input, AR, Table,
	           Accept, MailAddress, ConnectURL, Comments, Why),
	    RLValues = [Name|_],
	    (Accept = accept ->
	     db_update(NewPred, ErrorStruct, ValCode),
	     update_db,
	     ErrorStruct =..[_|ErrVals],
	     struct2list(ErrVals, Errors),
	     (Errors = [] ->
	      functor(TPack, Table, 28),
	      arg(1, TPack, AR),
	      itf_retract(TPack), % what about editions?:
	      db_url(URL),
%	      atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', AR, '&service=query_modify_service&action=amos_search&validated=yes'], ValidatedURL),
	      atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', ValCode, '&service=query_modify_service&action=amos_search&validated=yes'], ValidatedURL),
	      send_mail(Accept, Name, MailAddress, ValidatedURL, Comments, Why),
	      update_stats(Table, w),
	      text_option(element_validated, _, Message)
	     ;
	      text_option(element_with_errors, _, Message)
	     )
	    ;
	     text_option(element_rejected, _, Message),
	     send_mail(Accept, Name, MailAddress, ConnectURL, Comments, Why)
	    )
	  )
        ),
        html_option(consult_table_message,_, [Message], Reply1), !,
	(Errors == [] ->
	 get_form_value(Input, search, Session),
	 get_form_value(Input, currelem, LastElem),
	 (number(LastElem) ->
	  Elem is LastElem + 1,
	  search_vals(Session,Elem,Table, LFields, CType, Actions,Reply2,Form),
	  current_fact(search(Session, _, Length)),
	  (Length = 1 ->
	   Reply = Reply1
	 ;
	   Reply = [Reply1,\\,Reply2]
	   )
	 ;
	  input_field(Table, LFields, CType, Form),
	  Reply = Reply1
	 )	 
	;
	 arg(1, OldPred, AR),
	 amos_val_error_output(Table, OldPred, ErrVals, LFields, CType, Form),
	 Actions = "sofeidmw",
	 Reply = Reply1).
% Deletion of an element
full_reply(delete, Input, CType, Table, LFields, LValues, Actions, Reply, Form, _):-
	get_form_value(Input, deletion, yes), !,
	get_form_value(Input, searchmode, Search_Mode),
	(Table = 'Software_Description_Request' ->
	 get_form_value(Input, 'Software_Description_Request_1', Id),
	 (number(Id) ->
	  atom_number(AId, Id)
	 ;
	  AId = Id
	 ),
	 length(LV, 27),
	 prepare_search(Search_Mode, Table, [AId|LV], LPreds),
%	 display(LPreds), nl,
	 (LPreds = [] ->
	  text_option(deletion_failed,_,Message)
	 ;
	  itf_retractall(LPreds), % Delete everything !
	  text_option(deletion_complete,_,Message)
	 )
	;
	 (amos_entity(Table) ->
	  build_ditem(LValues, var, DItem,_,_),
	  findall(DItem, ditem_description(DItem,_), LDItems),
	  ditem_delete_all(LDItems), !
	 ;
	  prepare_search(Search_Mode, Table, LValues, LPreds),
	  itf_retractall(LPreds)
	 ), 
% Delete everything !
	 text_option(deletion_complete,_, Message)
	),
	input_field(Table, LFields, CType, Form),
        html_option(consult_table_message,_, [Message], Reply),
	(var(CType) ->
	 Actions="sofi"
	;
	 Actions="sofid"),
	update_stats(Table, d).
full_reply(delete, Input, CType, Table, LFields, LValues, "dsofi", Reply, Form, _):-
	get_single_query(Table, LValues, Pred),
	(Table = 'Software_Description_Request' ->
	 get_form_value(Input, 'Software_Description_Request_1', Id),
%	 display(d1(Id)),nl,
	 (number(Id) ->
	  atom_number(AId, Id)
	 ;
	  AId = Id
	 ),
	 arg(1, Pred, AId),
	 Form=[input(hidden, [name=deletion, value=yes]),
	       input(hidden, [name='Software_Description_Request_1',value=AId])|NF],
	 html_option(deletion_warning_user,_, [], Message)
	;
	 Form=[input(hidden, [name=deletion, value=yes])|NF],
	 html_option(deletion_warning,_, [], Message)
	),
	output(Table, Pred, LFields, CType, NF),
        html_option(consult_table_message,_, [Message], Reply).
% Search expired. Its contents will no longer be kept.
full_reply(_Command,_Input, CType,Table, LFields,_LValues, "sofid", Reply, Form, _):-
        input_field(Table, LFields, CType, Form),
        text_option(search_expired, _, Message),
        html_option(consult_table_message,_, [Message], Reply).

struct2list(EList, Errors) :-
	(all_var(EList) ->
	 Errors = []
	;
	 Errors = erroneous_fields).

all_var([]).
all_var([V|Vs]) :-
	var(V),
	all_var(Vs).

all_required([], []).
all_required([Desc|Ds], [Val|Vs]) :- !,
	(var(Val) ->
	 functor(Desc, F,_),
	 (is_required(F) ->
	  fail
	 ;
	  true)
 	;
 	 true),
	all_required(Ds, Vs).

is_required(name_rq_ck).
is_required(name_rq).
is_required(input_link_rq_ck).
is_required(invisible_searchable_date_rq).
is_required(textinput_rq_ck).
is_required(multiple_menu_rq_ck).

vars_to_void([], []).
vars_to_void([Val|Vs], [NewVal|Ns]) :-
	(var(Val) ->
	 NewVal = ''
	;
	 NewVal = Val),
	vars_to_void(Vs, Ns).

var_these(Pred, Ns, NPred) :-
	Pred=..[F|Args],
	var_these_list(Args, 1, Ns, NArgs),
	NPred =..[F|NArgs].

var_these_list([], _, _,[]).
var_these_list([Arg|As], I, Ns, [NArg|NAs]) :-
	(member(I, Ns) ->
	 var(NArg)
	;
	 NArg = Arg),
	I1 is I + 1,
	var_these_list(As, I1, Ns, NAs).	

welcome_mail(Values) :-
	Values = [Id, Name|_],
%	nth(20, Values, MailAddress), % 19+1. Starts at 2.
	nth(10, Values, MailAddress), % 9+1. Starts at 2.
%	nth(9, Values, MailAddress), % 8+1. Starts at 2.
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', Id, '&service=query_modify_service&action=amos_search'], ConnectURL),
	send_mail(insert, Name, MailAddress, ConnectURL, [], []).

verify(Fields, Values, Input, Id, Table, Accepted, MailAddress, 
	ConnectURL, Comments, Why) :-
	do_verify(Fields, Values, Input, Table, [], Why, MyAccepted),
	(var(MyAccepted) ->
	 Accepted = accept
	;
	 Accepted = reject),
%	nth(19, Values, MailAddress), % 21-2. Starts at 2.
	nth(9, Values, MailAddress), % 11-2. Starts at 2.
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', Id, '&service=query_modify_service&action=amos_search'], ConnectURL),
	get_form_value(Input, v_comment, Comments).
	
do_verify([], [], _, _, W, W, _) .
do_verify([Field|Fs], [Value|Vs], Input, Table, Why, FinalWhy, Accepted) :-
	atom_concat(Field, '_ck', Field_ck),
	get_form_value(Input, Field_ck, Tick),
	(Tick = on ->
	 NewWhy = Why
	;
	 Accepted = reject,
	 extract_number(Field, N),
	 current_fact(dbtable(Table, _, TableFieldsDesc, _, _)),
	 arg(N, TableFieldsDesc, FieldDesc),
	 arg(1, FieldDesc, Desc),
	 NewWhy = [[Desc, Value]|Why]
	),
	do_verify(Fs, Vs, Input, Table, NewWhy, FinalWhy, Accepted).

extract_number(Atom, Number) :-
	atom_codes(Atom, CAtom),
	reverse(CAtom, RCAtom),
	append(Preffix, [95], Preffix_), 
	append(Preffix_, _, RCAtom),
	reverse(Preffix, CNumber),
	number_codes(Number, CNumber).

send_mail(reject, Name, MailAddress, URL, Comments, Why) :- !,
	text_option(request_rejected_msg, _, Msg),
	text_option(request_rejected_subject, _, Subject0),
	atom_concat(Subject0, Name, Subject),
	do_send_mail(MailAddress, Subject, Msg, URL, Comments, Why).
send_mail(accept, Name, MailAddress, URL, Comments, Why) :- !,
	text_option(request_accepted_msg, _, Msg),
	text_option(request_accepted_subject, _, Subject0),
	atom_concat(Subject0, Name, Subject),
	do_send_mail(MailAddress, Subject, Msg, URL, Comments, Why).
send_mail(insert, Name, MailAddress, URL, Comments, Why) :- 
	atom(MailAddress), !, 
	text_option(insert_accepted_msg, _, Msg),
	text_option(insert_accepted_subject, _, Subject0),
	atom_concat(Subject0, Name, Subject),
	do_send_mail(MailAddress, Subject, Msg, URL, Comments, Why).
send_mail(edit, Name, MailAddress, URL, Comments, Why) :- 
	atom(MailAddress), !, 
	text_option(edit_msg, _, Msg),
	text_option(edit_subject, _, Subject0),
	atom_concat(Subject0, Name, Subject),
	do_send_mail(MailAddress, Subject, Msg, URL, Comments, Why).

admin_mail(Values) :-
	Values = [_Id,Name|_],
	db_url(URL),
	reviewer_email(AdminEMail), 
	atom_concat(URL, 'admin/amos_reviewer_access.cgi', ReviewURL),
	text_option(pending_validation_subject,_,Subject0),
	atom_concat(Subject0, Name, Subject),
	text_option(pending_validation_msg,_,Msg),
	do_send_mail(AdminEMail, Subject, Msg, ReviewURL, [], []).	

do_send_mail(MailAddress, Subject, Msg, URL, Comments, Why) :-
	text_option(amos_signature, _, Signature),
	open_output('/tmp/temp_sm_file', S),
	display(Msg), nl, nl,
	display(URL), nl, nl,
	((form_empty_value(Comments); Comments= []) ->
	 true
	;
	 display('General comments: '), nl,
	 display('================'),nl,
	 output_ll(Comments)
	),
	(Why = [] ->
	 true
	;
	 display('Rejection reasons:'), nl,
	 display('================='),nl,
	 show_whys(Why), nl
	),
	display(Signature), nl,
	close_output(S),
	atom_concat(['/bin/cat /tmp/temp_sm_file', ' | ', '/bin/mail -s ', '"', Subject, '"', ' ', MailAddress], Com),
 	system(Com),
	delete_file('/tmp/temp_sm_file').

show_whys([]).
show_whys([[Field, Value]|R]) :-
	display('Element '),
	display(Field),
	display(' has erroneous value: '),
	display(Value), nl,
	show_whys(R).

output_ll(A) :-
	atm(A), !,
	display(A), nl, nl.
output_ll([]) :- nl.
output_ll([Line|Ls]) :-
	atom_codes(ALine, Line),
	display(ALine), nl,
	output_ll(Ls) .

:- pred search_vals(+Session, +Elem, +Table, +LFields, -Actions, -Reply, -Form)
   # "After having made a one table search, this predicate generates a new 
      form @var{form} that will allow a new set of actions @var{Actions} 
      according to the circumstances of the previous search, as well as an 
      appropiate reply @var{Reply}. ".

search_vals(Session, Elem, Table, LFields, CType, Actions, Reply, Form) :-
        current_fact(search(Session, LPreds, Length)),
        ( (Elem > Length ; Elem < 1) ->
            text_option(no_found_reply, _, Message),
            html_option(consult_table_message,_, [Message], Reply),
            input_field(Table, LFields, CType, Form),
            Actions = "sofid"
        ; 
          member(pred(Elem, Pred), LPreds),
          output_search(Table, Pred, LFields, Session, Elem, CType, Form),
	  (Length = 1 ->
	   Reply = []
	  ;
           number_codes(Elem,ES), atom_codes(AElem,ES),
           number_codes(Length,LS), atom_codes(ALength,LS),
           html_option(match_message,_, [AElem,ALength], Reply)
	  ),
	  next_actions(Elem, Length, ActionsAux),
          (\+(Table = 'Software_Description_Request') ->
	   delete(ActionsAux, 0'w, Actions)
	  ;
	   Actions = ActionsAux
          )
        ).

:- pred make_refresh(+Session, +Input, +Table, +Arity, -NewActions, -Reply, -Data)

   # "After having made a full_search/5 operation this is commonly used to 
      update the tuples which may have been edited in such operation. It also
      generates a new form for a new one table query".

make_refresh(Session, Input, Table, Arity, CType, "sofid", Reply, Data):-
	current_fact(search(Session, LPreds, Length)),
	get_form_full_search_vals(Input, Table, 1, Length, Arity, LLValues),
	make_changes(LLValues, LPreds, Table, NewSessionVals),
	persdbrt:retract_fact(search(Session, LPreds, Length)),
	persdbrt:assertz_fact(search(Session, NewSessionVals, Length)),
	get_field_names(1, Table, Arity, LFields),
	input_field(Table, LFields, CType, Data),
	html_option(admin_short_message,_, ['Refreshment Completed. Make your next operation'], Reply),
	update_stats(Table, m).

get_form_full_search_vals(_, _, RowIndex, Length, _, []) :- 
	RowIndex > Length, !.
get_form_full_search_vals(Input, Table, RowIndex, Length, Arity, [LVals|LLVs]):- 
	get_full_search_vals(Input, Table, RowIndex, 1, Arity, LVals),
	NewRowIndex is RowIndex + 1,
	get_form_full_search_vals(Input, Table, NewRowIndex, Length, Arity, LLVs). 
get_full_search_vals(_, _, _, N, Arity, []):- N > Arity, !.
get_full_search_vals(Input, Table, RowIndex, N, Arity, [Val|Vs]):-
	number_codes(RowIndex, Rc),
	atom_codes(Ra, "_"||Rc),
	number_codes(N, Nc),
	atom_codes(Na, "_"||Nc),
	atom_concat(Ra, Na, Index),
	atom_concat(Table, Index, Field),
	get_form_value(Input, Field, Val),
	N1 is N + 1,
	get_full_search_vals(Input, Table, RowIndex, N1, Arity, Vs).

:- pred make_changes(+LVals, +Lpreds, +Table, -NewPreds)

   # "Retracts from the database the old data @var{LPreds} and updates it 
      with the new data @var{NewPreds} obtained from a list of values 
      @var{LVals}. ".

make_changes([], [], _, []).	
make_changes([LVals|LLVs], [pred(N,Pred)|Ps], Table, [pred(N, NewPred)|NPs]):-
	get_single_query(Table, LVals, NewPred),
	((form_empty_list_values(LVals)) ->
	 itf_retract(Pred)
	;
	 itf_retract(Pred),
 	 itf_assert(NewPred)),
	make_changes(LLVs, Ps, Table, NPs).

:- pred next_actions(+CurrentItem, +LastItem, -Actions)

   # " Determines in a one table query search which actions @var{Actions} will
       be allowed next according with the order of the current element 
       shown. ".

next_actions(1, 1, "sofeidmw") :- !.
next_actions(1, _, "asofeidmw") :- !.
next_actions(L, L, "sofbeidmw") :- !.
next_actions(_, _, "absofeidmw").

:- pred form_empty_list_values(+ListValues) # "Test if the list of
   values @var{ListValues} of a form is empty.".

form_empty_list_values([]).
form_empty_list_values([FieldValue|LFieldValues]):-
        (form_empty_value(FieldValue);
	  FieldValue = []),
        form_empty_list_values(LFieldValues).

:- pred set_actions(+Type, +ClientType, +Table, +ActionList, -Actions)

   # "Produces a checkpoint list @var{Actions} where the user can choose 
      the subsequent action. ".

set_actions(_,_,_, [], []).
set_actions(Type, ClientType, Table, ActionList, Actions) :-
        ActionList = [C|_],
        char_to_action(C, Default),
        allowed_actions(ClientType, Table, Acts),
        compute_actions(Type, Acts, ActionList, Default, Actions).

compute_actions(_,[], _, _, []).
compute_actions(Type, [C|Cs], AList, Def, [A|As]) :-
        member(C, AList), !,
        char_to_action(C, Act),
        compute_checked(Act,Def,Checked),
        text_option(Act,_,Text),
	(Type=action ->
	 Display_Template=action_display
	;
	 Display_Template=service_display),
	(Act = validate ->
	 html_option(suggestions,_,['',v_comment], ValField),
	 html_option(Display_Template, _, 
	             [Act, Text, Checked, ValField], A)		      
	;
         html_option(Display_Template,_, [Act, Text, Checked, []], A)),
        compute_actions(Type, Cs, AList, Def, As).
compute_actions(Type, [_|Cs], AList, Def, As) :-
        compute_actions(Type, Cs, AList, Def, As).

compute_checked(Def,Def,checked) :- !.
compute_checked(_, _, nochecked).

transparent_insert(V, Table, Next) :-
	allowed_actions(V, Table, Allowed),
	intersection(Allowed, Next, [0'i]).	

transparent_ov(V, Table, Next) :-
	allowed_actions(V, Table, Allowed),
	intersection(Allowed, Next, [0'o]).	

%Query actions
char_to_action(0'a, ahead).
char_to_action(0'b, back).
char_to_action(0'e, empty_form).
char_to_action(0's, search).
char_to_action(0'o, overview).
char_to_action(0'f, full_search).
char_to_action(0'r, refresh).
char_to_action(0'i, insert).
char_to_action(0'd, delete).
char_to_action(0'm, modify).
char_to_action(0'w, validate).
%Service actions
char_to_action(0't, system_status_service). 
char_to_action(0'c, db_connect_service). 
char_to_action(0'k, select_query_modify_service).
char_to_action(0'q, query_modify_service).
char_to_action(0'x, complex_query_service). 
char_to_action(0'p, modify_options_service). 
char_to_action(0'l, create_table_service). 
char_to_action(0'y, select_table_to_edit_service). 
char_to_action(0'n, table_to_edit_service). 
char_to_action(0'v, reload_templates_service). 

% allowed_actions(V,      "etkqidm") :- var(V), !.
% allowed_actions(Passwd, "abesofridmwtckqxplynv") :-
%         password(Passwd), !.
allowed_actions(V, Table, Acts) :- 
	var(V), !,
	(Table = 'Administration' ->
	 Acts = "etkqoidm"
	;
	 Acts = "etkqidm").
allowed_actions(Passwd,_, "abesofridmwtkqplynv") :-
        password(Passwd), !.
allowed_actions(Bad,_,_) :-
        bad_passwd(Bad).

:- pred overview_vals(+Session, +Table, +CType, -Reply, -Page)

   # "Generates an HTML table which displays the result of a one table search
      if an overview action was choosen. ".

overview_vals(Session, Table, CType, Reply, Page):-
	current_fact(search(Session, LPreds, Length)),
	current_fact(dbtable(Table, Arity, TableFieldsDesc, _, _)),
	TableFieldsDesc=..[_|LFieldsDesc],
	number_codes(Length,LS), atom_codes(ALength,LS),
	text_option(overview_message,_, Mess0),
	atom_concat(Mess0, ALength, MessAux),
	define_goods(Table, Goods),
	atom_concat(MessAux, Goods, Message),
	html_option(admin_short_message,_, [Message], Reply),
	ov_page(Table, Arity, CType, LPreds, LFieldsDesc, Page).

ov_page(Table, Arity, CType, LPreds, LFieldsDesc, Page):-
	get_heading(LFieldsDesc, HeadData),
	display(HeadData), nl,
	make_row(HeadData, FirstRow),
	get_rows(LPreds, Table, CType, LFieldsDesc, RRows),
	Rows=[FirstRow|RRows],
	number_codes(Arity, A),
	atom_codes(AtomArity, A),
	html_option(general_table,_, [AtomArity, Rows], Page).

define_goods('Dictionary', ' dictionary items').
define_goods('Administration', ' packages').
:- pred full_search(+Table, +Session, +Passwd, -Reply, -Page)

   # "Generates an HTML table which displays the result of a one table search
      if a full search action was choosen. It also allows editing these 
      results. ".

full_search(Table, Session, CType, Reply, Page):-
	current_fact(search(Session, LPreds, Length)),
	current_fact(dbtable(Table, _, TableFieldsDesc, _, _)),
	TableFieldsDesc=..[_|LFieldsDesc],
	number_codes(Length,LS), atom_codes(ALength,LS),
	atom_concat('Resulting tuples: ', ALength, Message),
	html_option(admin_short_message,_, [Message], Reply),
	full_search_page(Table, Session, CType, LPreds, LFieldsDesc, Page).

full_search_page(Table, Session, CType, LPreds, LFieldsDesc, Page):-
	get_heading(LFieldsDesc, HeadData),
	make_row(HeadData, FirstRow),
	current_fact(dbtable(Table, Arity,_,_,_)),
	get_full_search_fields(CType, LPreds, Table, Arity, LFieldsDesc, 1, RRows),
	Rows=[FirstRow|RRows],
	number_codes(Arity, A),
	atom_codes(AtomArity, A),
	html_option(general_table,_, [AtomArity, Rows], P),
	Page=[input(hidden, [name=search, value=Session])|P].

get_full_search_fields(_,[],_,_,_,_,[]).
get_full_search_fields(CType, [pred(_N,Pred)|Ps], Table, Arity, LFieldsDesc, RowIndex, [Row|Rs]):-
	Pred=..[_|Vals],
	get_row_field(CType, Vals, Table, RowIndex, Arity,  LFieldsDesc, RowData),
	make_row(RowData, Row),
	NewRowIndex is RowIndex+1,
	get_full_search_fields(CType, Ps, Table, Arity, LFieldsDesc, NewRowIndex, Rs).
	
get_row_field(CType, LVals, Table, RowIndex, Arity,  LFieldsDesc, LRowFields):-
	make_field_names(Table, RowIndex, 1, Arity, LFields),
	output_field_table_elem(CType, LFields, LFieldsDesc, LVals, LRowFields).
		 
get_rows([],_,_,_,[]). 
get_rows([pred(_N,I1)|Is], Table, CType, FieldsTypes,  [R1|Rs]):- 
	I1=..[_|Fields],
	get_row(Fields, FieldsTypes, RowData0, Key),
	display(row(RowData0)), nl,
	(Table = 'Administration' -> 
	 build_admin_links(Key, CType, Links),
	 append(RowData0, Links, RowData)
	;
	 RowData0 = RowData),
%	make_row(RowData, R1),
	html_option(table_row2, _, [RowData], R1),
	get_rows(Is, Table, CType, FieldsTypes, Rs).

get_row([],_,[],_).
get_row([Field|Fs], [_Type|Ts], [FinalField|Ds], Key):-
	var(Field), !,
	NewField = '',
	html_option(table_element,_, [NewField], FinalField),
	get_row(Fs, Ts, Ds, Key).
get_row([Field|Fs], [Type|Ts], FinalFields, Key):-
	functor(Type, TypeName, _),
	((number(Field), is_a_number_type(TypeName)) ->
	  atom_number(NewField, Field),
	  html_option(table_element,_, [NewField], FinalField),
	  FinalFields = [FinalField|Ds]  
	;
	  is_invisible(TypeName) ->
	  (var(Key) -> Key = Field; true),
	   FinalFields = Ds
	;
	  html_option(table_element,_, [Field], FinalField),
	  FinalFields = [FinalField|Ds]
	),
	get_row(Fs, Ts, Ds, Key).
	
is_a_number_type(number).
is_a_number_type(number_ck).
is_a_number_type(number_rq).
is_a_number_type(number_rq_ck).

is_invisible(invisible).
is_invisible(invisible_searchable).
is_invisible(invisible_searchable_date).
is_invisible(invisible_rq).
is_invisible(invisible_searchable_rq).
is_invisible(invisible_searchable_date_rq).

is_hideable(T) :-
	is_invisible(T).
is_hideable(T) :-
	is_a_number_type(T).

get_heading([], []).
get_heading([FieldDesc|FDs], Fs):-
	functor(FieldDesc, TypeName, _),
	is_invisible(TypeName), !,
	get_heading(FDs, Fs).
get_heading([FieldDesc|FDs], [NewField|Fs]):-
	arg(1, FieldDesc, Field),
	html_option(table_heading_elem, _, [Field], NewField),
	get_heading(FDs, Fs).

get_heading_straight([], []).
get_heading_straight([Field|FDs], [NewField|Fs]):-
	html_option(table_heading_elem, _, [Field], NewField),
	get_heading_straight(FDs, Fs).

make_row(RowData, Row):-
	html_option(table_row, _, [RowData], Row).

make_field_names(_Table, _RowIndex, N, Arity, []):- N > Arity, !.
make_field_names(Table,  RowIndex, N, Arity, [Field|Fs]):-
	number_codes(RowIndex, Rc),
	atom_codes(Ra, "_"||Rc),
	number_codes(N, Nc),
	atom_codes(Na, "_"||Nc),
	atom_concat(Ra, Na, Index),
	atom_concat(Table, Index, Field),
	N1 is N + 1,
	make_field_names(Table,  RowIndex, N1, Arity, Fs).

:- pred set_footer_CGI(+Password, -Footer, -CGI) 

   # "Sets the CGI client to interact with the user and the right footer 
      depending on the password produced. ".

set_footer_CGI(V, Footer, CGI):- var(V), !,
	html_option(common_footer,_,[], Footer),
	platform_cgi('webdb_client', CGI).
set_footer_CGI(Passwd, Footer, CGI):-
        password(Passwd), !,
	html_option(admin_footer,_, [], Footer),
	platform_cgi('admin/db_generic_client', CGI).
set_footer_CGI(Bad, _, _) :-
        bad_passwd(Bad).

:- pred full_reply_page(+Title, +Body, -OutputPage)

   # "Given a title @var{Title} and a body @var{Body}, generates an
      HTML page @var{OutputPage} with a fix structure. ".

full_reply_page(Title, Body, [
	cgi_reply,
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
	nl,
	html$[
		 xmlns="http://www.w3.org/1999/xhtml",
		 'xml:lang'="en"
	     ], nl,
	begin(head), nl,
	meta$[
		 'http-equiv'="content-type",
		 content="application/xhtml+xml; charset=utf-8"
	     ], nl,
	meta$[
		 name="author",
		 content="JMG & The CLIP Group"
	     ], nl,
	meta$[
		 name="generator",
		 content="Pillow/Emacs"
	     ], nl,
	link$[
		 rel="stylesheet",
		 type="text/css",
		 href=CSS,
		 title="Amos stylesheet"
	     ], nl,
	end(head),
	base$[href=URL],
	title(Title),
	Body,
	end]) :- db_url(URL),
	         db_css(CSS).


:- pred enumerate(+LPreds, -LNumPreds, -Length) 

   # "Creates a list @var{LnumPreds} of predicates @tt{pred(Number,
      Pred)} where @tt{Number} gives the order of @tt{Pred} in the new
      list given a previous list of predicates
      @var{LPreds}. @var{Length} is the length of the list.".

enumerate(LPreds, LNumPreds, Length):-
	enumerate_(LPreds, 0, LNumPreds, Length).

enumerate_([P|LPreds], C, [pred(Counter,P)|LNumbPreds], Lentgh):-
	Counter is C+1,
	enumerate_(LPreds, Counter, LNumbPreds, Lentgh).
enumerate_([], C ,[], C).

	
:- pred get_single_query(+Table, +LFieldValues, -Pred) 

   # "Gets a Prolog query @var{Pred} given a relation name @var{Table}
      and its values @var{LFieldValues}.".

get_single_query(Table, LFieldValues, Pred):-
	turn2terms(LFieldValues, LFieldTerms),
	current_fact(dbtable(Table, _,_,SiteId, _)),
	(SiteId = local ->
	 Pred=..[Table|LFieldTerms]
	;
	 get_remote_table_name(SiteId, Table, Functor),
	 Pred=..[Functor|LFieldTerms]).

get_remote_table_name(SiteId, Table, TableName):-
	atom_concat('_', SiteId, SSiteId),
	atom_concat(TableName, SSiteId, Table).

:- pred turn2terms(+Values, -Terms) 

   # "Converts a List of HTML values @var{Values} into a list of
      Prolog Terms @var{Terms}.".

turn2terms([], []).
turn2terms([Value|Values], [Term|Terms]):-
	( (form_empty_value(Value); var(Value))->
	  var(Term)               %just declarative
       ; 
	  Value = [] -> 
	  var(Term)               %just declarative
	;
	  Term=Value
	),
	turn2terms(Values, Terms).

:- pred output_search(+Table, +Pred, +LFields, +Session, +Elem, -Form) 

   # "Prepares the next page for an user to keep interacting with the system 
      through the one table query interface. ".

output_search(Table, NPred, LFields, Session, Elem, CType, Form) :-
        output(Table, NPred, LFields, CType, F),
        Form = [input(hidden, [name=search, value=Session]),
                input(hidden, [name=currelem, value=Elem])
                |F].

:- pred output(Pred, LFields, Form) 

   # "Gives the output form fields @var{Form} for an HTML form with fields 
      @var{LFields} using the arguments of @var{Pred} as the new default 
      values.".

output(Table, Pred, LFields, CType, Form):-
       html_option(general_table,_,['2',OutputFields], Form),
       current_fact(dbtable(Table, _, Types, _, _)),
       Types=..[_|LTypes],
       Pred=..[_|LValues],
       output_field(LFields, LTypes, LValues, CType, OutputFields).

output_field([], [], [], _, []).
output_field([F|Fs], [T|Ts], [V|Vs], CType, [R|Rs]) :-
       type_val_info(T, V, Format, DescriptionAux),
       (var(CType) ->
	 DescriptionAux = Description,
	 html_format(Format, F, OutputField,_, public)
	;
	 html_format(Format, F, OutputField,_, private),
	 functor(Format, Func, _),
	 (Func = invisible_searchable ->
	  Description = 'State'
	 ;
	  Func = invisible_searchable_date_rq ->
	  Description = 'Submission Date'
	 ;
	  DescriptionAux = Description)
	),
	html_option(data_row,_,[Description, OutputField], R),
        output_field(Fs, Ts, Vs, CType, Rs).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Kludge to especially capture amos packages validation errors                %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

amos_val_error_output(Table, Pred, Errors, LFields, CType, Form):-
	Pred=..[_|LValues],
	prepare_search(normal, Table, LValues, LPreds),
	register_search(LPreds, Session),
       Form = [input(hidden, [name=search, value=Session]),
               input(hidden, [name=currelem, value='1'])
               |F],
       html_option(general_table,_,['2',OutputFields], F),
       current_fact(dbtable(Table, _, Types, _, _)),
       Types=..[_|LTypes],
       amos_val_error_output_field(LFields, LTypes, LValues, 
	                            Errors, CType, OutputFields).

amos_val_error_output_field([], [], [], [], _, []).
amos_val_error_output_field([F|Fs], [T|Ts], [V|Vs], [E|Es], CType, [R|Rs]) :-
       type_val_info_ave(T, V, E, Format, DescriptionAux),
	(var(CType) ->
	 DescriptionAux = Description,
	 html_format(Format, F, OutputField,_, public)
	;
	 html_format(Format, F, OutputField,_, private),
	 functor(Format, Func, _),
	 (Func = invisible_searchable ->
	  Description = 'State'
	 ;
	  Func = invisible_searchable_date_rq ->
	  Description = 'Submission Date'
	 ;
	  DescriptionAux = Description)
	),
        html_option(data_row,_,[Description, OutputField], R),
        amos_val_error_output_field(Fs, Ts, Vs, Es, CType, Rs).

type_val_info_ave(Type, Val, Error, Format, Desc) :-
        Type =.. [T,Desc,_|Args],
	(var(Error) ->
	 Format =.. [T,Val|Args]
	;
	 \+ atom_concat(_, '_ck', T) ->
	 Format =.. [T,Val|Args]
	;
	 Args = [Length, _],
	 Format =.. [T,Val,Length,off]
	).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

output_field_table_elem(_, [], [], [], []).
output_field_table_elem(CType,[Field|Fs],[Type|Ts], [Val|Vs], [RowField|RFs]):-
	type_val_info(Type, Val, Format, _),
	(var(CType)-> 
	 DisplayMode=private
	;
	 DisplayMode=public),
	(Format=..[number, Value, N] -> 
	 (var(Val) -> 
	  NewFormat=number('', N)
	 ;
	  number_codes(Value, ValueList),
	  atom_codes(AValue, ValueList),
	  NewFormat=number(AValue, N)),
	 html_format(NewFormat, Field, RowFieldAux, _,DisplayMode) 
	;
	html_format(Format, Field, RowFieldAux, _,DisplayMode)),
	html_option(table_element, _, [RowFieldAux], RowField),
	output_field_table_elem(CType, Fs, Ts, Vs, RFs).

:- pred input_field(+Table, +LFields, -Form) 

   # "Generates a form that allows querying a particular table @var{Table}. ".

input_field(Table, LFields, CType, Form) :-
        html_option(general_table,_,['2',InputFields], Form),
        current_fact(dbtable(Table, _, Types, _, _)),
        Types=..[_|LTypes],
        input_field_(LFields, LTypes, CType, InputFields).

input_field_([], [], _,[]).
input_field_([F|Fs], [T|Ts], CType, [R|Rs]) :-
       type_info(T, Format, DescriptionAux),
	(var(CType) -> 
	 DescriptionAux = Description,
	 html_format(Format, F, InputField,_, public)
	;
	 html_format(Format, F, InputField,_, private),
	 functor(Format, Func, _),
	 (Func = invisible_searchable ->
	  Description = 'State'
	 ;
	  Func = invisible_searchable_date_rq ->
	  Description = 'Submission Date'
	 ;
	  DescriptionAux = Description)
	),
	html_option(data_row,_, [Description, InputField], R),
       input_field_(Fs, Ts, CType, Rs).

:- pred type_val_info(+Type, +Val, -Format, -Desc)

   # "Given a dbtables_desc/3 syntax HTML type @var{Type} and its value 
     @var{Val}, obtains its right HTML format @var{Format} and description 
     @var{Desc}. ".

type_val_info(Type, Val, Format, Desc) :-
        Type =.. [T,Desc,_|Args],
        Format =.. [T,Val|Args].

:- pred type_info(+Type, -Format, -Desc)

   # "Given a dbtables_desc/3 syntax HTML type @var{Type}, obtains its 
      right HTML format @var{Format} and description  @var{Desc}. ".

type_info(Type, Format, Desc) :-
        Type =.. [T,Desc|Args],
        Format =.. [T|Args].

:- pred clear_old_sessions(Session) # "Cleans the system of obsolete
   sessions. It eliminates every previous session to Session which is
   older than Elapsed seconds.".

clear_old_sessions(Session):-
	search_expire(Elapsed),
	PrevSession is Session - Elapsed,
	search(S, _, _), S < PrevSession,
	persdbrt:retract_fact(search(S, _, _)),
        fail.
clear_old_sessions(_).

:- pred complex_query(+Input, +ClientType, -Response) 
   :: dic * passwd * output_page

   # "Implements the SQL/Prolog Syntax search service. Provides an 
      interface which allows querying the database in both
      Prolog-like and SQL syntax. This way, the user can operate on the
      database just as if from the Prolog top level or from the SQL
      command line. ".

complex_query(Input,Passwd, Response) :-
	get_form_value(Input, query_began, yes), !,
	process_query(Input, Reply, Page),
        set_footer_CGI(Passwd, _Footer, _),
        text_option(complex_query_title,_, Title),
	text_option(complex_query_response,_, ResponseH),
        html_option(admin_heading,_, [ResponseH], Head),
        html_option(page_body,_, [[Head, Content, []]], Body),
        html_option(admin_content,_, [ComplexqDB], Content),
	ComplexqDB=[Reply,
	            Page
		   ],
        full_reply_page(Title, Body, Response).	

complex_query(_Input,Passwd, Response) :-
	findall(table_info(Table_Name, Arity, TFD, Site), 
	        dbtable(Table_Name, Arity, TFD, Site, _), TList),
        display_tables_info(TList, TInfo),
        html_option(general_table,_,[2,[TInfo]], TInfoHTML),
	html_option(complex_query_instructions, _, [], Instructions), 
	text_option(complex_query_head, _, CQHead),
	html_option(general_heading, _, [CQHead], Mess),
	text_option(complex_query_title,_, Title),
	findall(Loc, sql_persistent_location(Loc, _), Locations),
	tables_locations([local|Locations], Options),
        html_option(admin_heading,_, [Title], Head),
        html_option(admin_content,_, [Complex_table], Content),
	text_option(choose_database_loc_mess, _, CDText),
	html_option(general_choose,_,[CDText], Loc_Prompt),
	set_footer_CGI(Passwd, Footer, CGI),
        html_option(ok_button,_, [], Ok_Button),
        html_option(reset_button,_, [], Reset_Button),
        html_option(form_buttons,_, [Ok_Button, Reset_Button], Buttons),
	Complex_table = [
			 Mess,
			 TInfoHTML,
			 start_form(CGI),
 			 Loc_Prompt,
			 select([name=database_name], Options),
 			 Instructions,
			 textinput(query, [rows=10, cols=50], 
                                   "Please, write here your query"), 
			 input(hidden, [name=query_began, value=yes]),
			 input(hidden, [name=service, 
			                value=complex_query_service]),
                         Buttons,
                         end_form
                        ],
	html_option(page_body,_, [[Head, Content, Footer]], Body),
        full_reply_page(Title, Body, Response).

:- pred process_query(+Input, +Reply, -Response)

   # "Analizes the query written by the client in Prolog or SQL syntax and 
      generates a table @var{Response} with the variables involved and 
      their matching values. ".

process_query(Input, Reply, Response):-
	get_form_value(Input, query, AtomQuery),
	get_form_value(Input, database_name, DB),
	get_form_value(Input, language, Lang),
	current_output(OldOutput),
        open('/tmp/temp_qfile', write, S),
        set_output(S),
        write(AtomQuery), 
	(Lang=on-> write('.');true),
	nl,
        set_output(OldOutput),
        close(S),
        open('/tmp/temp_qfile', read, Stream),
	error_protect(read_term(Stream, Query, 
                      [variables(Vs), variable_names(Ns)])),
	delete_file('/tmp/temp_qfile'),		       
	(Lang=on ->
	 error_protect(sql_query(DB, Query, R)),
	 R=..[_|[TermList]],
	 TermList=[Tuple|_],
	 length(Tuple, Arity),
	 process_output([], TermList, Arity, Reply, Response)
	 ;
	 validate(Query, DB, Res),
	 (Res=ok ->
	  R=..[r|Vs],
	  functor(R,r,Arity),
	  error_protect(itf_findall(DB, R, Query, L)),
	  order_var_names(Ns, Vs, NsFinal),
	  process_output(NsFinal, L, Arity, Reply, Response)
	 ;
	  text_option(unsuccessful_query,_, Unsuccesful),	    
	  html_option(admin_short_message,_, [Unsuccesful], Reply),
	  Response=[]
	 )),
	update_stats(_, c).
process_query(_, Reply, []):-
         text_option(unsuccessful_query,_, Unsuccesful),	    
	 html_option(admin_short_message,_, [Unsuccesful], Reply).

:- pred validate(+Query, +DB, -Res)

   # "If in prolog syntax, checks the predicates involved in the query 
      @var{Query} are all @pred{authorized/1}. ".

validate(Query, DB, Res):-
	functor(Query, F, _A),
	(F==',';F==';'), !,
	arg(1, Query, SubQ1),
	arg(2, Query, SubQ2),
	validate(SubQ1, DB, Res1),
	validate(SubQ2, DB, Res2),
	(Res1=ok, Res2=ok ->
	 Res=ok
	;
	 Res=ko).
validate(Query, DB, Res):-
	functor(Query, F, A),
	(authorized(F/A, DB)->
	 Res=ok
	;
	 Res=ko).	

authorized(F/A,_):-
	dbtable(F,A,_,_,_),!.
authorized(F/A, DB):-
	\+(DB=local),
	atom_concat([F,'_',DB], F_DB),
	dbtable(F_DB,A,_,_,_),!.
authorized('+'/2,_):-!.
authorized('-'/2,_):-!.
authorized('*'/2,_):-!.
authorized('/'/2,_):-!.
authorized('\+'/2,_):-!.
authorized('='/2,_):-!.
authorized('=='/2,_):-!.
authorized('>'/2,_):-!.
authorized('<'/2,_):-!.
authorized('>='/2,_):-!.
authorized('=<'/2,_):-!.
authorized('is'/2,_):-!.
authorized('=:='/2,_):-!.
authorized('=/='/2,_):-!.

order_var_names(_,[],[]).
order_var_names(NamesEqVar, [GuideVar|Vs], [Name|RNs]):-
	member_esp((Name=GuideVar), NamesEqVar),
	order_var_names(NamesEqVar,Vs, RNs).

member_esp((X=Z), [(X=Y)|_]):- Z==Y, !.
member_esp(X, [_|R]):- member_esp(X,R).

:- pred process_output(+Vars, +Rows, +Arity, -Reply, -Page)

   # "Formats the output of a given query in Prolog/SQL syntax. ".

process_output(_,[],_, Reply, []):- !,
	text_option(no_found_reply, _, Mess),
	html_option(admin_short_message,_, [Mess], Reply).
process_output(Vs, Rs, Arity, Reply, Page):-
	get_general_heading(Vs, Heading),
	make_row(Heading, Row1),
	get_general_rows(Rs, table_row, RRows),
	Rows=[Row1|RRows],
	number_codes(Arity, A),
	atom_codes(AtomArity, A),
	html_option(general_table,_, [AtomArity, Rows], Page),
	html_option(admin_short_message,_, 
	            ['Complex query resulted in:'], Reply).

get_general_rows([],_,[]). 
get_general_rows([I1|Is], RowType, [R1|Rs]):- 
	(I1=[_|_] ->
	 Args=I1
	;
	 I1=..[_|Args]
	),
	get_general_row(Args, RowData),
%	make_row(RowData, R1),
	html_option(RowType, _, [RowData], R1),
	get_general_rows(Is, RowType, Rs).

get_general_row([], []).
get_general_row([Arg|As],[FinalArg|FAs]):-
	atom(Arg),!,
	html_option(table_element,_, [Arg], FinalArg),
	get_general_row(As, FAs).
get_general_row([Number|As],[FinalArg|FAs]):-
	number(Number), !,
	number_codes(Number, CN),
	atom_codes(Arg, CN),
	html_option(table_element,_, [Arg], FinalArg),
	get_general_row(As, FAs).
get_general_row([Term|Es],[FinalArg|FAs]):-
	Term = a(_,_), !,
	html_option(table_element,_, [Term], FinalArg),
	get_general_row(Es, FAs).
get_general_row([_Var|Vs],[FinalArg|FAs]):-
	text_option(free_var_warning,_,Warning),
	html_option(table_element,_, [Warning], FinalArg),
	get_general_row(Vs, FAs).
	
get_general_heading([], []).
get_general_heading([VarName|Vs], [NewVarName|Ns]):-
	html_option(table_heading_elem, _, [VarName], NewVarName),
	get_general_heading(Vs, Ns).

:- pred tables_locations(+Locations, -Menu)

   # "Displays a menu where the user can choose the location a table of a 
      database belongs to. ".

tables_locations([],[]).
tables_locations([local|Locations], [O1|Opts]):-
	!,O1 = option([value=local], local),
	tables_locations(Locations, Opts).
tables_locations([L1|Locations], [O1|Opts]):-
	sql_persistent_location(L1,db(_,U, _, _:_)),
	O1 = option([value=L1], U),
	tables_locations(Locations, Opts).

:- pred display_tables_info(+Tables, -TablesInfo)

   # "Displays a table with information about the tables contained in the 
      system, their number of arguments and their field types. ".

display_tables_info([], []).
display_tables_info([table_info(Table_Name, Arity, TFD, Site)|TInfos], 
	            [Table_Info_Html|Ts]):-
	table_info(Table_Name, Arity, TFD, Site, Table_Info_Html),
	display_tables_info(TInfos, Ts).

table_info(TN, A, TFD, Site,THTML):-
	number_codes(A, NA),
        atom_codes(CA, NA),
	(Site = local ->
	 TDN = TN,
	 U = Site
	;
         atom_concat('_',Site, TNAux),
	 atom_concat(TDN, TNAux, TN),
    	 sql_persistent_location(Site, db(_, U, _, _:_))
	),
	atom_concat([TDN, '/', CA, ', ', 'laying on view: ', U], PredName),
	html_option(table_heading_elem, _, [PredName], NameElem),
	TFD=..[_|FieldsTypes],
	format_types(FieldsTypes, TFields),
	html_option(table_element, _, [TFields], TFs),
	html_option(data_row, _, [NameElem, TFs], THTML).

format_types([], []).
format_types([Fd|FieldsTypes], [Tf |TFields]):-
	Fd=..[Functor|Args],
	concat_args(Args, CArgs),
	atom_concat([Functor, '(', CArgs, ')'], AFd),
	html_option(field_desc, _, [AFd], Tf),
	format_types(FieldsTypes, TFields).

concat_args([A], CA):-
	get_atom_with_suffix(A,'',CA).
concat_args([A|As], CAs):-
	get_atom_with_suffix(A, ',', CA),
	concat_args(As, CAsAux),
	atom_concat(CA, CAsAux, CAs).

get_atom_with_suffix(Code, Suffix, Atom):-
	atom(Code), !,
	atom_concat([Code, Suffix], Atom).
get_atom_with_suffix(Number, Suffix, Atom):-
	number_codes(Number, CNumber),
        atom_codes(ANumber, CNumber),
	atom_concat(ANumber, Suffix, Atom).

:- pred updatetemplates(+ClientType, -Response) :: passwd * output_page

   # "This service is used to reload the HTML templates, which control
      the settings, or any of the files that define the types used,
      after having edited any of them. This way, the system
      administrator can customize the system on-line, with no need to
      stop it.  Only authorised clients can use this service.".

updatetemplates(Passwd, Response) :-
	check_passwd(Passwd),
	read_option_html(_),
	create_ini_page,
	include_parametric_files,
	text_option(templates_title,_, Title),
	text_option(templates_reloaded,_, Message),
	short_response(Title, Message, Response).

:- pred consultdboptions(+Input, +ClientType, -Response) 
   :: dic * passwd * output_page

   # "Implements the online system messages edit service. Provides 
      an interface to edit on line every system message and,
      in general, any text which appears in the System's user
      interface. For example, the text of any link, the heading or
      title of any page or the message given when an element has been
      successfully inserted.  Only authorised clients can use this
      service.".

consultdboptions(Input, Passwd, Response) :-
      check_passwd(Passwd),
      get_form_value(Input, selected, yes), !,
      update_changed_values(Input),
      create_ini_page_if_needed,
      text_option(options_title,_, Title),
      text_option(options_changed,_, Message),
      short_response(Title, Message, Response).
consultdboptions(_Input,_Passwd, Response) :-
      findall(Option, option_entry(Option), FOps),
      html_option(options_body,_,[FOps], AllOps),
      text_option(options_title,_, Title),
      text_option(option_message,_, OMText),
      html_option(general_add,_,[OMText], Message),
      html_option(admin_heading,_, [Title], Head),
      html_option(admin_content,_, [Change_option], Content),
      html_option(admin_footer,_, [], Footer),        
      html_option(ok_button,_,[], Ok_Button),
      html_option(reset_button,_, [], Reset_Button),
      html_option(form_buttons,_, [Ok_Button, Reset_Button], Buttons),
      platform_cgi('admin/db_generic_client', CGI),
      Change_option = [Message,
                      start_form(CGI),
		        AllOps,
			 input(hidden, [name=selected, value=yes]),
			 input(hidden, [name=service, value=modify_options_service]),
                      Buttons,
		        end_form
                      ],
      html_option(page_body,_, [[Head, Content, Footer]], Body),
      full_reply_page(Title, Body, Response).

update_changed_values(Input) :-
       current_fact(text_option(Name, Desc, Val)),
       member(Name=NewVal, Input),
       Val \== NewVal,
       persdbrt:retract_fact(text_option(Name,_,_)),
       persdbrt:assertz_fact(text_option(Name,Desc,NewVal)),
       check_ini_page_change(Name),
       fail.
update_changed_values(_).

:- pred create_destroy_table(+Input, +ClientType, -Response) 
   :: dic * passwd * output_page

   # "Implements the Create/Destroy database table service. 
      Provides an interface where the user can create a new table or
      destroy an already existing one, or both at the same
      time. Tables created this way have arity zero and need to be
      subsequently edited through the service @pred{edit_table/3}.
      Only authorised clients can use this service.".

create_destroy_table(Input,Passwd, Response) :-
       check_passwd(Passwd),		
       operate_on_tables(Input),
       findall(TI, (dbtable(TI, _, _, _, _),
                    \+ TI= 'Dictionary',
                    \+ TI= 'Administration',
                    \+ TI= 'Software_Description_Request',
		    \+ TI = 'Software_Description_Validation'), TIList),
       table_options(TIList, Options),
       text_option(table_creation_title,_, Title),
       html_option(general_heading, _, [Title], Mess),
       text_option(create_table_message,_, Message),
       text_option(destroy_table_message,_, Message2),
       html_option(table_element, _, [Message],Create_Mess),
       html_option(table_element, _, [Message2],Destroy_Mess),
       html_option(table_element, _, [input(text, [name=new_table_name, size=40])],
         CreateInput),
	html_option(table_element, _, [select([name=list],
	 [option([value=not_destroy], 'Keep current tables')|Options])],Destroy_List),
	make_row([Create_Mess, CreateInput], CreateRow),
	make_row([Destroy_Mess, Destroy_List], DestroyRow),
	html_option(general_table,_, ['2', [CreateRow, DestroyRow]], Creation_table),
       html_option(admin_heading,_, [Title], Head),
       html_option(admin_content,_, [Add_table], Content),
       html_option(admin_footer,_, [], Footer),        
       html_option(ok_button,_, [], Ok_Button),
       html_option(reset_button,_, [], Reset_Button),
       html_option(form_buttons,_, [Ok_Button, Reset_Button], Buttons),
       platform_cgi('admin/db_generic_client', CGI),
       Add_table = [
		       Mess,\\,
		       start_form(CGI),
		       Creation_table,			 
		       input(hidden,[name=selected,value=yes]),
		       input(hidden,[name=service,value=create_table_service]),
		       Buttons,
		       end_form
                        ],
	html_option(page_body,_, [[Head, Content, Footer]], Body),
	full_reply_page(Title, Body, Response).

:- pred operate_on_tables(+Input)

   # "From the user interface it gets which table has to be added or destroyed 
      or both in the system and does so. ".

operate_on_tables(Input):-
	get_form_value(Input, selected, yes), !,
	get_form_value(Input, new_table_name, New_Table_Name),
	get_form_value(Input, list, Destroy_Table), 
	add_new_table(New_Table_Name),
	destroy_table(Destroy_Table).
operate_on_tables(_).

add_new_table(Table_Name):-
	form_empty_value(Table_Name), !.
add_new_table(Table_Name):-
	date(Date),
        persdbrt:assertz_fact(dbtables_desc(Table_Name, local, Date)),
	data(Table_Name/0),
	make_persistent(Table_Name/0, db),
	data_facts:assertz_fact(dbtable(Table_Name, 0, Table_Name, local, Date)),
	initialize_stat(Table_Name).


destroy_table(not_destroy):- !.
destroy_table(TI):-
	data_facts:current_fact(dbtable(TI, Arity, FieldsDesc, local, Date)),!,
	get_free_vars(1, Arity, VarList),
	Pred=..[TI|VarList],
	pretract_all(Pred),
	persistent_dir(db, Dir),
	number_codes(Arity, NA),
	atom_codes(CA, NA),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '.pl'], File),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '_ops.pl'], File_ops),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '_bak.pl'], File_bak),
	delete_file1(File),
	delete_file1(File_ops),
	delete_file1(File_bak),
	persdbrt:retract_fact(dbtables_desc(FieldsDesc, local, Date)),
	data_facts:retract_fact(dbtable(TI, Arity, FieldsDesc, local, Date)),
	reset_stat(TI).	
destroy_table(TI):-
	current_fact(dbtable(TI, Arity, FieldsDesc, SiteId, Date)),
	persdbrt:retract_fact(dbtables_desc(FieldsDesc, SiteId, Date)),
	data_facts:retract_fact(dbtable(TI, Arity, FieldsDesc, SiteId, Date)),
	reset_stat(TI),!,
	(call(dbtable(_,_,_,SiteId,_)) ->
	 true
	;
	 unlink_DB(SiteId)).
destroy_table(_).

pretract_all(P):-
	persdbrt:retract_fact(P),
	fail.
pretract_all(_).

:- pred select_table_to_edit(+Passwd, -Response) :: passwd * output_page

   # "Implements with @pred{edit_table/3} the edit table type service. 
      Similar to @pred{get_relationsdb/2}, provides a menu to choose the
      table whose field types the user wants to edit.  Only authorised
      clients can use this service.".

select_table_to_edit(Passwd, Reply):-
	check_passwd(Passwd),
	text_option(edit_table_title,_, Title),
	text_option(edit_table_heading,_,Heading),
	html_option(admin_heading,_, [Heading], Head),
	html_option(admin_content,_, [Choose_table], Content),
	text_option(choose_table_edit_mess, _,CTText),
	html_option(general_choose,_,[CTText],Message),
	html_option(ok_button,_,[],Ok_Button),
	html_option(admin_footer, _, [], Footer),
       html_option(page_body,_, [[Head, Content, Footer]], Body),
       findall(TI, (dbtable(TI, _, _, local,_), 
                    \+ TI= 'Dictionary',
                    \+ TI= 'Administration',
                    \+ TI= 'Software_Description_Request',
		    \+ TI = 'Software_Description_Validation'), TIList),
       table_options(TIList, Options),
       platform_cgi('admin/db_generic_client', CGI),
       Choose_table = [
			  Message,
			  start_form(CGI),
			  select([name=table_name], Options),
			  input(hidden, [name=initial, value=yes]),
			  input(hidden, [name=service, 
			                 value=table_to_edit_service]),
                        \\, \\,
                        Ok_Button,
                        end_form
		        ],
        full_reply_page(Title, Body, Reply).

:- pred edit_table(+Input, +Passwd, -Response) :: dic * passwd * output_page 

   # "Once the user has selected through @pred{select_table_to_edit/2}
      the table to be edited, provides an interface which allows the
      user to edit any type of its fields or add a new one with a
      given type.  Only authorised clients can use this service.".

edit_table(Input, Passwd, Response):-
       check_passwd(Passwd),
       get_form_value(Input, table_name, Table),
       set_edit_answer(Input, Table, Table_out, Message, Edition_table),
       html_option(admin_footer, _, [], Footer),
       text_option(edit_table_title,_, Title),
       text_option(edit_table_heading,_,Heading),
       html_option(page_body,_, [[Head, Content, Footer]], Body),
       html_option(admin_heading,_, [Heading], Head),
       html_option(admin_content,_, [Edit_table], Content),        
       html_option(ok_button,_,[],Ok_Button),
       html_option(reset_button,_,[],Reset_Button),
       html_option(form_buttons,_,[Ok_Button, Reset_Button], Buttons),
       text_option(add_field_message,_, AFText),
       html_option(general_add, _, [AFText], Add_field_message),
       html_option(table_element, _, 
       [input(text, [name=add_field_desc, size=25])], NewFieldDesc),
	findall(option([value=Option], Option), 
	        (option_table_size(Option, _), Option\==none), 
		 Options),
	html_option(table_element,_, 
	      [select([name=add_field_type],[none|Options])], NewFieldType),
	make_row([NewFieldDesc,NewFieldType], Add_field_row),  
	html_option(general_table,_, ['2', Add_field_row], Added_row_table),
	platform_cgi('admin/db_generic_client', CGI),
	Edit_table=[Message,
                   start_form(CGI),
		     Edition_table,
		     \\, \\,
		     Add_field_message,
		     Added_row_table,
		     input(hidden, [name=table_name, value=Table_out]),
		     input(hidden, [name=service, value=table_to_edit_service]),
		     Buttons,
		     end_form
		   ],
        full_reply_page(Title, Body, Response).	

set_edit_answer(Input, TI, TI_out, Message, Edition_table):-
	set_edit_mode(Input, TI, TI_out),
	current_fact(dbtable(TI_out, Arity, TableDescFields, local,_)),
	TableDescFields=..[_|Types],
	get_field_types(1, Types, Rows),        
	(Arity = 0 ->
	 Message = []
	;
	 text_option(edit_table_message, _, ETText),
	 html_option(general_choose,_,[ETText],Message)),
	html_option(general_table,_, ['2', Rows], Edition_table).

set_edit_mode(Input, TI, TI):-
	get_form_value(Input, initial, yes), !.
set_edit_mode(Input, TI, TI_out):-
	update_table(Input, TI, TI_out).

update_table(Input, Table, TI_out):-
	get_form_value(Input, table_name, Table),
	get_form_value(Input, add_field_desc, New_desc),
	get_form_value(Input, add_field_type, New_type),
	current_fact(dbtable(Table, Arity, _, local, _)),
	get_form_full_search_vals(Input, edit_line, 1, Arity, 2, LLVals),
	(form_empty_value(New_desc) ->
	 compose_table_types(LLVals, TableTypes)
	;
	 append(LLVals, [[New_desc, New_type]], New_pred_desc),
	 compose_table_types(New_pred_desc, TableTypes)),
	NewFieldsDesc=..[Table|TableTypes],
	edit_dbtables(Table, NewFieldsDesc, TI_out).
	
compose_table_types([], []).
compose_table_types([[_Desc,none]|LLVs], TTs):-
	compose_table_types(LLVs, TTs).
compose_table_types([[Desc,TypeOption]|LLVs], [TypeTerm|TTs]):-
	option_table_size(TypeOption, TypeTerm),
	(form_empty_value(Desc) ->
	 arg(1, TypeTerm, anonymous)
	;
	 arg(1, TypeTerm, Desc)),
	compose_table_types(LLVs, TTs).
	
:- pred edit_dbtables(+TI, +NewFieldsDesc, -TI)

   # "Actually updates a table after having changed it description. The old 
      data is inconsistent, therefore eliminated. ".

edit_dbtables(TI, NewFieldsDesc, TI):-
	data_facts:current_fact(dbtable(TI, _Arity, FieldsDesc, local,_)),
	NewFieldsDesc = FieldsDesc, !.
edit_dbtables(TI, NewFieldsDesc, NTI):-
	data_facts:current_fact(dbtable(TI, Arity, FieldsDesc, local, _)),
	persdbrt:retract_fact(dbtables_desc(FieldsDesc, local, _)),
	date(Date),
	persdbrt:assertz_fact(dbtables_desc(NewFieldsDesc, local, Date)),
	get_free_vars(1, Arity, VarList),
	Pred=..[TI|VarList],
	pretract_all(Pred),
	functor(NewFieldsDesc, _F, NewArity),
	data_facts:assertz_fact(dbtable(TI, NewArity, NewFieldsDesc, local, Date)),
	update_dyn_pred(TI, Arity, NewArity),
	NTI=TI,
	persistent_dir(db, Dir),
	number_codes(Arity, NA),
        atom_codes(CA, NA),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '.pl'], File),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '_ops.pl'], File_ops),
	atom_concat([Dir, 'dbserver:', TI, '-', CA, '_bak.pl'], File_bak),
	delete_file1(File),
	delete_file1(File_ops),
	delete_file1(File_bak), 
	data_facts:retract_fact(dbtable(TI, Arity, FieldsDesc, local, _)),
	reset_stat(TI),
	initialize_stat(TI),!.
edit_dbtables(_, _, _).

delete_file1(File):-
	(file_exists(File) ->
	 delete_file(File)
	;
	 true).

get_free_vars(N, A, []):- N > A, !.
get_free_vars(N, A, [V|Vs]):-
	var(V),
	N1 is N + 1,
	get_free_vars(N1, A, Vs).

update_dyn_pred(P, A, NA):-
	A \== NA, !,
	data(P/NA),
	make_persistent(P/NA, db).
update_dyn_pred(_, _, _).

get_field_types(_, [], []).
get_field_types(Index, [Type|Ts], [Edit_line|Es]):-
	type_info(Type, Format, Desc),
	process_type(Format, Option),
	functor(Type, F, _N),
	(Option='' ->
	 FinalOption=F
	;
	 atom_concat([F, '_', Option], FinalOption)),
	make_edit_line(Index, Desc, FinalOption, Edit_line),	
	NewIndex is Index + 1,
	get_field_types(NewIndex, Ts, Es).	

:- pred make_edit_line(+Index, +Desc, +Opt, -Edit_line)

   # "Provides a new part of the interface where the user can write the name
      of the new field and choose its type. ".

make_edit_line(Index, Desc, Opt, Edit_line):-
	make_field_names(edit_line,  Index, 1, 2, [Field1, Field2]),
	findall(option([value=Option], Option),
	 option_table_size(Option, _), OptionList),
	select(option([value=Opt], Opt), OptionList, OptionRest),
	NewOptList=[option([value=Opt], Opt)|OptionRest],
	html_option(table_element, _, 
	[input(text, [name=Field1, size=25, value=Desc])], RowField1),
	html_option(table_element, _, 
	[select([name=Field2], NewOptList)], RowField2),
	make_row([RowField1, RowField2], Edit_line).

:- pred process_type(+Format, -Option)

   # "Given an HTML format @var{Format}, obtains a text description for 
      the size of the input field that correlates with it. ".

process_type(Format, Option):-
	html_format(Format,_,_,unidimensional,_), !,
	arg(2, Format, Length),
	get_option_size(Length, Option).
process_type(Format, Option):-
	html_format(Format,_,_,bidimensional,_), !,
	arg(2, Format, Rows),
	arg(3, Format, Cols),	
	get_option_bi_size(Rows, Cols, Option).
process_type(Format, ''):-
	html_format(Format,_,_,plain,_).

get_option_size(L, 'small (1-20)'):- L > 0, L < 21, !.
get_option_size(L, 'medium (21-40)'):- L > 20, L < 41, !.
get_option_size(L, 'large (41-60)'):- L > 40, L < 61, !.
get_option_size(_, none).

get_option_bi_size(R, C, 'small (Rows*Cols <= 200)'):- R*C < 201, !.
get_option_bi_size(R, C, 'medium (Rows*Cols <= 400)'):- R*C < 401, !.
get_option_bi_size(R, C, 'large (Rows*Cols <= 1200)'):- R*C < 1201, !.
get_option_bi_size(_, _, none).

:- data ini_page_changed/0.

check_ini_page_change(Name) :-
        ini_page_text_option(Name) ->
            data_facts:asserta_fact(ini_page_changed)
        ; true.

:- pred create_ini_page_if_needed 

   # "If anything in the graphic display of the system's front page has changed
      the system finds it out automatically and generates it over again. ".

create_ini_page_if_needed :-
        ini_page_changed,
        data_facts:retractall_fact(ini_page_changed),
        create_ini_page.
create_ini_page_if_needed.

option_entry(Entry) :-
        text_option(Name, Desc, Val),
        html_option(option_entry,_,[Name,Desc,Val],Entry).

:- pred update_stats(+TI, +Action)

   # "Updates the system statistics for every action and database table. ".

update_stats(TI, s):- !,
	itf_retract(table_stats(TI, S, I, D, M, W, _)),
	inc_field_info(S, New_S, Date),
	itf_assert(table_stats(TI, New_S, I, D, M, W, Date)).
update_stats(TI, i):- !,
	itf_retract(table_stats(TI, S, I, D, M, W, _)),
	inc_field_info(I, New_I, Date),
	itf_assert(table_stats(TI, S, New_I, D, M, W, Date)).
update_stats(TI, d):- !, 
	itf_retract(table_stats(TI, S, I, D, M, W, _)),
	inc_field_info(D, New_D, Date),
	itf_assert(table_stats(TI, S, I, New_D, M, W, Date)).
update_stats(TI, m):- !,
	itf_retract(table_stats(TI, S, I, D, M, W, _)),
	inc_field_info(M, New_M, Date),
	itf_assert(table_stats(TI, S, I, D, New_M, W, Date)).
update_stats(TI, w):- !,
	itf_retract(table_stats(TI, S, I, D, M, W, _)),
	inc_field_info(W, New_W, Date),
	itf_assert(table_stats(TI, S, I, D, M, New_W, Date)).
update_stats(_, c):- !,
	itf_retract(table_stats('$Complex queries', S, I, D, M, W, _)),
	inc_field_info(S, New_S, Date),
	itf_assert(table_stats('$Complex queries', New_S, I, D, M, W, Date)).
update_stats(_, _).

inc_field_info(N, N1, Date):-
	N1 is N + 1,
	date(Date).

:- pred status(Response) => output_page

   # "Implements the system status service. Returns information about the 
      system itself and the tables it holds.".

status(Response) :-
        text_option(status_title,_, Title),
	text_option(status_message,_, SMText),	
	launch_date(LDate),
	atom_concat(SMText, LDate, SSText),
        html_option(general_add,_,[SSText], Message),
	html_option(admin_heading,_, [Title], Head),
        html_option(page_body,_, [[Head, Content, []]], Body),
        html_option(admin_content,_, [Status], Content),
 	findall(t(T,S,I,D,M,W,CD,L), (table_stats(T,S,I,D,M,W,L), 
 	                            dbtable(T,_,_,_,CD)), List),
	table_stats('$Complex queries', Sc, Ic, Dc, Mc, Wc, Lc),
	text_option(c_q_access,_,CQH),
	StatsList=[t(CQH, Sc, Ic, Dc, Mc, Wc, Lc, Lc)|List],
	text_option(search_access,_,Search),
	text_option(insert_access,_,Insert),
	text_option(delete_access,_,Delete),
	text_option(modify_access,_,Modify),	
	text_option(validate_access,_,Validate),	
	text_option(created_on,_,Created),
	text_option(last_accessed,_,Last),	
	text_option(table_prompt,_,TName),	
	text_option(total_access,_,TotalPrompt),		
	text_option(global_stats_heading,_,GSH),
	text_option(det_stats_heading,_,DSH),
	html_option(general_heading,_, [GSH], GSHead),
	html_option(general_heading,_, [DSH], DSHead),
	get_general_heading([TName,Search,Insert,Delete,Modify,Validate,Created,Last], THeading),
	get_general_rows(StatsList, table_row, RRows),
	make_row(THeading, R1),
	Rows=[R1|RRows],
	html_option(general_table,_,['9',Rows], TStats),
	get_total_parts(StatsList, 0, 0, 0, 0, 0, St, It, Dt, Mt, Wt),
	TotalAccesses is St + It + Dt + Mt + Wt,
	get_general_heading([Search, Insert, Delete, Modify, Validate, TotalPrompt], TotalHeading),
	make_row(TotalHeading, Tr1),
	get_general_rows([[St, It, Dt, Mt, Wt, TotalAccesses]], table_row, Trow),
	Trows=[Tr1|Trow],
	html_option(general_table,_,['6',Trows], GTStats),
	Status=[GSHead,
	%	Graphic,
	        GTStats,
	        Message,
		DSHead,
	        TStats
		   ],
        full_reply_page(Title, Body, Response).

get_total_parts([], S, I, D, M, W, S, I, D, M, W).
get_total_parts([t(_, S, I, D, M, W, _,_)|RTs ], Sx, Ix, Dx, Mx, Wx, Sf, If, Df, Mf, Wf):-
	Saux is Sx + S,
	Iaux is Ix + I,
	Daux is Dx + D,
	Maux is Mx + M,
	Waux is Wx + W,
	get_total_parts(RTs, Saux, Iaux, Daux, Maux, Waux, Sf, If, Df, Mf, Wf).

:- pred get_graphic(+St, +It, +Dt, +Mt, -Graph)

   # "Obtains a graphic representation of the number of actions (searches, 
      insertions, deletions and modifications). ".

get_graphic(St, It, Dt, Mt, Graph):-
	current_output(OldOutput),
        open('/tmp/data', write, S),
        set_output(S),
	write_data_to_file(1,4,[St, It, Dt, Mt]),
        set_output(OldOutput),
        close(S),
        current_executable(Exe),
        absolute_file_name(Exe,'','','.',_,_,Dir),
        atom_concat(['gnuplot ',Dir,'/graph '], StatScript),
	system(StatScript),
	db_dir(DBDir),
	atom_concat(['mv -f /tmp/graph.gif ', DBDir, '/stats'], MVCommand),
	system(MVCommand),
	html_option(stats,_, ['stats/graph.gif'], Graph),!.
get_graphic(_, _, _, _, []).

write_data_to_file(M,N,[D|Ds]):-
	M<N+1, !,
	write(M), write(' '), write(D), nl,
	M1 is M+1,
	write_data_to_file(M1, N, Ds).
write_data_to_file(_, _, []).

:- comment(die/1,"Once the database has been updated in order to keep
   all the information in a consistant state, it halts the database
   server.  Only authorised clients can use this service.").

:- pred die(ClientType) : passwd

   # "Used to kill the server (active module) after updating the database.".

die(Passwd) :-
        check_passwd(Passwd), 
	initialize_db,
	update_local_db,
	get_addr_dir(DirAddr,_),
	get_lock_file(DirAddr, Lock_File),
	delete_file(Lock_File),
	halt.

update_local_db:-
	dbtable(T,A,_, _, _),
	make_persistent(T/A, db),
        fail.
update_local_db.

:- pred dying(+ClientType, -Response) :: passwd * output_page

   # "Implements with @pred{die} the stop the system service. 
      Before having halted the database server through @pred{die/1},
      this predicate is used to produce a message, pointing out that
      the system is about to be halted.  Only authorised clients can
      use this service.".

dying(Passwd, Response) :-
        check_passwd(Passwd), 	
        text_option(stop_title,_, Title),
        text_option(stop_message,_, Message),
        short_response(Title, Message, Response).

short_response(Title, Message, Response) :-
	html_option(page_body,_, [[Head, Content]], Body),
        html_option(admin_heading,_, [Title], Head),
        html_option(admin_short_message,_,[Message], Mess),
        html_option(admin_content,_, [Mess], Content),
        full_reply_page(Title, Body, Response).

check_passwd(Passwd) :-
        nonvar(Passwd), password(Passwd), !.
check_passwd(Bad) :-
        bad_passwd(Bad).

bad_passwd(Bad) :-
        inform_user(['WARNING! Bad passwd sent: ',Bad,
                     ' ; maybe somebody is attacking the system']),
        halt.

:- initialization(startupdb).

:- pred startupdb # "Reads the configuration files of the database. If
   the source file is found, it creates the maintenance and common client 
   front pages and dies.".

get_addr_dir(DirAddr, Exe) :-
        current_executable(Exe),
        absolute_file_name(Exe,'','','.',_,_,Dir),
        atom_concat(Dir,'/.addr',FAddr),
	open_input(FAddr, SFAddr),
	read(DirAddr),
	close_input(SFAddr).

get_lock_file(DirAddr, LockFile) :-
	atom_concat(DirAddr,'/.lock', LockFile).

startupdb:-
	umask(_, 0o007),
	get_addr_dir(DirAddr, E),
	get_lock_file(DirAddr, Lock_file),
	get_pid(PID),
	open_output(Lock_file, SS),
	write(PID),
	close_output(SS),
	atom_concat(E, '_ini.pl', File),
	open_input(File, IO), !,
 	repeat,
	  read(T),
	  process(T),
	T == end_of_file, !,
	close_input(IO),
	make_desc_persistent,
	include_parametric_files,
	read_option_html(_),
	(exists_pl(E) ->
	 init_persdb,
	 read_db_tables,
	 create_ini_page,
	 password(Passwd),
	 die(Passwd)
       ; 
	 retractall(dbtable(_,_,_,_,_)),
	 init_persdb,
	 include_sql_locations,
	 read_db_tables
       ),
       date(Date),
       data_facts:asserta_fact(launch_date(Date)),
       initialize_stat('$Complex queries').

exists_pl(E) :-
        atom_concat(E,'.pl',File),
        file_exists(File).
% Kludge for windows
exists_pl(E) :-
        atom_concat(E1,'.cpx', E),
	atom_concat(E1, '.pl', File),
        file_exists(File).

process(end_of_file) :- !.
process(db_os(OS)) :- !,
	data_facts:asserta_fact(db_os(OS)).
process(db_dir(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(db_dir(DIR)).
process(db_types(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(db_types(DIR)).
process(db_templates(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(db_templates(DIR)).
process(db_url(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(db_url(DIR)).
process(db_css(CSS)) :- !,
	data_facts:asserta_fact(db_css(CSS)).
process(db_description(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(persistent_dir(db_description, DIR)).
process(db_location(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(persistent_dir(db_location, DIR)).
process(db_data(Dir)) :- !,
	add_final_slash(Dir, DIR),
	data_facts:asserta_fact(persistent_dir(db, DIR)).
process(reviewer_email(EMail)) :- !,
	data_facts:asserta_fact(reviewer_email(EMail)).
process(Bad) :-
	inform_user(['Bad data in init file: ',Bad]).

:- data text_option/3.
:- data search/3.
:- data sql_location/2.
:- data table_stats/7.
:- data dbtables_desc/3.
:- data random_id/1.
:- data suggestions/1.


make_desc_persistent :-
	make_persistent(text_option/3, db_description),
	make_persistent(search/3, db_description),
	make_persistent(sql_location/2, db_description),
	make_persistent(table_stats/7, db_description),
	make_persistent(dbtables_desc/3, db_description),
	make_persistent(suggestions/1, db_description),
	make_persistent(random_id/1, db_description).

add_final_slash(Dir, DIR) :-
        atom_concat(_,'/',Dir) -> DIR = Dir ; atom_concat(Dir, '/', DIR).

include_sql_locations:-
	sql_location(DBId, DBHandle),
	data_facts:assertz_fact(sql_persistent_location(DBId, DBHandle)),
	fail.
include_sql_locations.

read_db_tables :-
	current_fact(dbtables_desc(Table, SiteId, Date)),
        functor(Table, F, A),
	(SiteId = local ->
         data_facts:assertz_fact(dbtable(F, A, Table, local, Date)),
	 data(F/A),
	 TName=F,
	 make_persistent(F/A, db)
	;
	 atom_concat([F,'_',SiteId], TName),
	 data_facts:assertz_fact(dbtable(TName, A, Table, SiteId, Date)),
	 get_sql_args(Table, PrologDef, SQLDef),
	 make_sql_persistent(PrologDef, SQLDef, SiteId)),
	initialize_stat(TName),
	fail.
read_db_tables.

initialize_stat(TName):-
	call(table_stats(TName,_,_,_,_,_,_)), !.
initialize_stat(TName):-
	date(Date),
	itf_assert(table_stats(TName,0,0,0,0,0,Date)).

reset_stat(TName):-
	itf_retract(table_stats(TName,_,_,_,_,_,_)).

reset_stats(DbId):-
	dbtable(TI,_,_,DbId,_),
	reset_stat(TI),
	fail.
reset_stats(_).

include_parametric_files:-
	include_cl_parametric_file('html_format.pl', html_format(_,_,_,_,_)),
	include_parametric_file('html2sql.pl', html2sql(_,_)),
	include_parametric_file('sql2html.pl', sql2html(_,_,_)),
	include_parametric_file('option_table_size.pl',option_table_size(_,_)),
	include_parametric_file('dbconn_desc.pl',dbconn_desc(_,_,_,_,_,_)),
	include_parametric_file('search_expire.pl',search_expire(_)).

include_cl_parametric_file(File, Pattern):-
	db_types(Dir),
	atom_concat(Dir, File, CFile),
	(retractall(Pattern);true),
	open_input(CFile, IO), !,
	read(Clause),
	process_cl_file(Clause),
	close_input(IO).

include_parametric_file(_,_).

include_parametric_file(File, Pattern):-
	db_types(Dir),
	atom_concat(Dir, File, CFile),
	(data_facts:retractall_fact(Pattern);true),
	open_input(CFile, IO), !,
	read(Fact),
	process_file(Fact),
	close_input(IO).

include_parametric_file(_,_).

process_file(end_of_file):- !.
process_file(Fact):-
	data_facts:assertz_fact(Fact),
	read(NewFact),
	process_file(NewFact).

process_cl_file(end_of_file):- !.
process_cl_file(Clause):-
	dynamic:assertz(Clause),
	read(NewClause),
	process_cl_file(NewClause).

%%%%%%%%%%%%%%% System utility predicates %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

date(Date) :-
	datime(X),
	X=..[_|DTList],
	get_atoms(DTList, [Year, Month, Day, _Hour, _Min, _Sec]),
	atom_concat([Year, '/', Month, '/', Day], Date).

get_atoms([], []).
get_atoms([N|Ns], [A|As]) :-
	number_codes(N,NList), 
	atom_codes(A, NList),
	get_atoms(Ns, As).

atom_number_if_needed(AId, Id) :-
	 (number(Id) ->
	  atom_number(AId, Id)
	 ;
	  AId = Id).

add_spaces([], []).
add_spaces([V], [V]).
add_spaces([Val0|V0s], [Val0, ' '|Vs]) :-
	add_spaces(V0s, Vs).

db_to_plain(AId, LPreds) :-
	db_recover(AId, Pred),
%	display(recovered(Pred)), nl, nl,
	Pred =..[_|Args],
	format_args(Args, FArgs),
	NPred =.. ['Software_Description_Request'|FArgs],
%	display(go(NPred)), nl,nl,
	LPreds = [NPred].

format_args([], []).
format_args([Arg|As], [FArg|Fs]) :-
	format_arg(Arg, FArg),
	format_args(As, Fs).
	
format_arg(Var, '') :-
	var(Var), !.
format_arg(N, A) :-
	number(N), !,
	atom_number(A, N).
format_arg('$null$', '') :- !.
format_arg(L, LL) :- 
	list(L), !,
	atom_list_to_l_l(L, LL).
format_arg(Arg, Arg).

atom_list_to_l_l([], []).
atom_list_to_l_l([A|As], [Al|Als]) :-
	atom(A), !,
	(is_a_ditem(A) ->	 
	 A = Al
	;
	 atom_codes(A, Al)),
	atom_list_to_l_l(As, Als).
atom_list_to_l_l([A|As], [A|Als]) :-
	atom_list_to_l_l(As, Als).

is_a_ditem(A) :-
	ditem_description(I,_),
	ditem_entry(I,A,I).


delete_if_needed(Input) :-
	get_form_value(Input, 'Software_Description_Request_1', Id),
	atom_number_if_needed(AId, Id),
%	display(deleting(AId)), nl,
	resource_delete(AId).
delete_if_needed(_).

not_validated(AId) :-
	atom_concat(_A, '_tmp', AId).

build_admin_links(Key, CType, [L1]) :-
	var(CType), !,
	show_package_link(Key, L1).
build_admin_links(Key, _CType, Links) :-
	Links = [L1,L2,L3,L4],
	show_package_link(Key, L1),
	edit_package_link(Key, L2),
	send_to_author_package_link(Key, L3),
	delete_package_link(Key, L4).

show_package_link(Key, Link) :-
	text_option(show_package,_, Text),
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?key=', Key, '&service=show_package'], Link0),
	L1=env(a, [href=Link0], [Text]),
	html_option(table_element,_, [L1], Link).

edit_package_link(Key, Link):-
	text_option(edit_package,_, Text),
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', Key, '&service=query_modify_service&action=amos_search&validated=yes'], Link0),
	L1=env(a, [href=Link0], [Text]),
	html_option(table_element,_, [L1], Link).

send_to_author_package_link(Key, Link) :-
	text_option(send_to_author,_, Text),
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?key=',Key,'&service=send_to_author'],Link0),
	L1=env(a, [href=Link0], [Text]),
	html_option(table_element,_, [L1], Link).

delete_package_link(Key, Link) :-
	text_option(delete_package,_, Text),
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?key=',Key,'&service=delete_package'],Link0),
	L1=env(a, [href=Link0], [Text]),
	html_option(table_element,_, [L1], Link).


filter_(V, V) :-
	list(V), !.
filter_(V, V0) :-
	atom_concat(V0, '(*)', V), !.
filter_(V, V0) :-
	atom_concat('http',_, V), !,
	V0 = env(a, [href=V], [V]).
filter_(V, V).

no_list(Pred0, Pred) :-
	Pred0 =.. [F|A0s],
	no_list_aux(A0s, As),
	Pred =.. [F|As].

no_list_aux([], []).
no_list_aux([A0|A0s], [A0|As]) :-
	var(A0),
	no_list_aux(A0s, As).
no_list_aux([A0|A0s], [A|As]) :-
	list(A0), !,
	l_l_to_atom(A0, A),
	no_list_aux(A0s, As).
no_list_aux([A0|A0s], [A0|As]) :-
	no_list_aux(A0s, As).

l_l_to_atom([], '').
l_l_to_atom([L|Ls], Atom) :-
	atom_codes(A, L),
	l_l_to_atom(Ls, As),
	atom_concat(A, As, Atom).

%%%%%%%%%%%%%%% Special predicates for persdb_sql handling %%%%%%%%%%%%%%%%%%%%
get_sql_args(Table, PrologDef, SQLDef):-
	Table=..[TName|TFields],
	get_fields_ids_types(TFields, IDs, Types),
	SQLDef=..[TName|IDs],
	PrologDef=..[TName|Types].

get_fields_ids_types([], [], []).
get_fields_ids_types([T1|Types], [Id1|IDs], [TT1|TTs]):-
	type_info(T1, _, Id1),
	html2sql(T1, TT1),
	get_fields_ids_types(Types, IDs, TTs).

%%%%%%%%%%%%%%%%%%%%%%%%%% Interface predicates %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

itf_assert(Tuple):-
	dbassertz_fact(Tuple), !.
itf_assert(Tuple):-
	persdbrt:assertz_fact(Tuple), !.
itf_assert(Tuple):-
	data_facts:assertz_fact(Tuple).

itf_retract(Tuple):-
 	dbretract_fact(Tuple), !.
itf_retract(Tuple):-
	persdbrt:retract_fact(Tuple), !.
itf_retract(Tuple):-
	data_facts:retract_fact(Tuple), !.
itf_retract(_).

itf_retractall([]).
itf_retractall([P|Ps]):-
	itf_retract(P),
	itf_retractall(Ps).

itf_retractall_fail(Fact):-
	itf_retract(Fact),
	fail.
itf_retractall_fail(_).

itf_findall(local, Recipient, Template, ResultList):-
	!, findall(Recipient, Template, ResultList).
itf_findall(DBId, Recipient, Template, ResultList):-
 	dbfindall(DBId, Recipient, Template, ResultList).

itf_call(local, Pred):- 
	!, call(Pred).
itf_call(SiteId, Pred):-
 	dbcall(SiteId, Pred).

%%%%%%%%%%%%%%%%%%%%%%%%%%html templates description%%%%%%%%%%%%%%%%%%%%%%

option(page_body, [content]).
option(maintenance_link, [url,name]).
option(anchor_link, [anchor, name, desc]).
option(maintenance_footer, []).
option(admin_heading, [title]).
option(main_page_heading, [title]).
option(admin_content, [content]).
option(admin_short_message, [message]).
option(admin_footer, []).
option(common_footer, []).
option(ok_button, []).
option(reset_button, []).
option(ok_button_first, []).
option(reset_button_first, []).
option(form_buttons, [ok, reset]).
option(consult_table_heading, [table, link]).
option(consult_table_message, [content]).
option(consult_table_message2, [content1, content2]).
option(match_message, [current,total]).
option(data_row, [name, value]).
option(control_row, [act, mode]).
option(action_display, [action, text, checked, extra]).
option(service_display, [action, text, checked, extra]).
option(general_choose, [text]).
option(deletion_warning, []).
option(deletion_warning_user, []).
option(deletion_warning_user2, []).
option(option_entry, [name,text,value]).
option(options_body, [content]).
option(general_table, [arity, contents]).
option(no_frame_table, [arity, contents]).
option(table_row, [tuple]).
option(table_row2, [tuple]).
option(table_heading_elem, [value]).
option(table_element, [value]).	
option(table_added_message, [newtable]).
option(create_table_message, []).
option(general_sel_mess, [text]).
option(search_mode_display, [searchmode, text, checked]).
option(general_heading, [text]).
option(complex_query_instructions, []).
option(field_desc, [fdesc]).
option(db_connection_message, []).
option(big_bold_message, [text]).
option(general_add, [text]).
option(stats, [pic]).
option(suggestions, [text, field]).

:- pred read_option_html(Name) 

   # "Transforms the html templates files into prolog terms which can be 
      used by Pillow. ".

read_option_html(Name) :-
       db_templates(Dir),
       option(Name, Params),
       create_dict(Params, Dict, Vars),
       atom_concat([Dir,Name, '.html'], HTML_file),
       ( file_to_string(HTML_file, Contents),
	 html_template(Contents, HTML_terms, Dict),
	 (get_option(HTML_terms, Title, HTMLOption) -> 
	  true
        ; 
	  inform_user(['Bad/inexistent HTML file ',HTML_file]),
         fail
	 )
       ),
       data_facts:retractall_fact(html_option(Name, _, _, _)),
       data_facts:assertz_fact(html_option(Name, Title, Vars, HTMLOption)),
       fail.
read_option_html(_).

create_dict([], [], []).
create_dict([N|Ns], [N=V|NVs], [V|Vs]) :-
        create_dict(Ns, NVs, Vs).

get_option([E|Es], Title, HTMLOption) :- !,
        ( E = env(html,_,Contents) ->
              get_option2(Contents, Title, HTMLOption)
	 ; string(E) ->
	       get_option2(Es, Title, HTMLOption)
        ; get_option(Es, Title, HTMLOption)
        ).
get_option(env(html,_,Contents), Title, HTMLOption) :- !,
	get_option2(Contents, Title, HTMLOption).

get_option2([E|Es], Title, HTMLOption) :-
        ( E = env(head,_,InHead) ->
              get_title(InHead, Title),
              get_option3(Es, HTMLOption)
        ; E = env(title,_,[Title]) ->
              get_option3(Es, HTMLOption)
        ; get_option2(Es, Title, HTMLOption)
        ).

get_title([env(title,_,[Title])|_], Title) :- !.
get_title([_|Es], Title) :-
        get_title(Es, Title).

get_option3(env(body,[],HTMLOption), HTMLOption) :- !. % Body with no atts
get_option3(Es, HTMLOption) :- get_body_content(Es, HTMLOption), !.
get_option3(HTMLOption, HTMLOption).

get_body_content([E|_], HTMLOption) :-
        E = env(body,Atts,Contents), !,
        ( Atts = [] -> HTMLOption = Contents ; HTMLOption = E ).
get_body_content([_|Es], HTMLOption) :-
        get_body_content(Es, HTMLOption).

ini_page_text_option(maintenance_title).
ini_page_text_option(maintenance_heading).
ini_page_text_option(webdb_title).
ini_page_text_option(webdb_heading).
ini_page_text_option(webdb_system_link).
ini_page_text_option(maintenance_system_link).
ini_page_text_option(stop_system_link).
ini_page_text_option(start_system_link).
ini_page_text_option(system_status_service).
ini_page_text_option(query_modify_service).
ini_page_text_option(modify_options_service).
ini_page_text_option(reload_templates_service).
ini_page_text_option(complex_query_service).
ini_page_text_option(create_table_service).
ini_page_text_option(select_table_to_edit_service).
ini_page_text_option(db_connect_service).


%%%%%%%%%%%%%%%%% AMOS additional services %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

format_values([], [], []).
format_values([Val0|Vs], [D|Ds], Entries) :-
	arg(1, D, Name),
	functor(D, F,_),
	((is_hideable(F); Val0 = ''; Val0 = []) ->
	 Entries = Es
	;
 	 (F = multiple_menu_ck ->
	  add_spaces(Val0, Val)
 	 ;
 	  Val0 = Val),
	 filter_(Name, Name_filtered),
	 filter_(Val, Val_filtered),
	 html_option(data_row, _, [Name_filtered,Val_filtered], Entry),
%	 display(e(Entry)), nl,
	 Entries = [Entry|Es]),
	format_values(Vs, Ds, Es).

show_package(Input, Response) :-
	get_form_value(Input, key, Key),
	db_recover(Key, Pred),
	Pred =.. [_|Values0],
	format_args(Values0, Values),
	dbtable('Software_Description_Validation',_, Table,_,_),
	Table =.. [_|FieldDescs],
	format_values(Values, FieldDescs, FValues),
	html_option(general_table,_, ['2', FValues], AllValues),
	text_option(values_title,_, Title),
	text_option(value_message,_, SPText),
	html_option(general_add,_,[SPText], Message),
	html_option(admin_heading,_, [Title], Head),
	html_option(admin_content,_, [ValueList], Content),
	html_option(admin_footer,_, [], Footer),        
	ValueList = [
			Message,
			AllValues
		    ],
	html_option(page_body,_, [[Head, Content, Footer]], Body),
	full_reply_page(Title, Body, Response).

send_to_author(Input, Response) :-
	get_form_value(Input, key, Key),
	db_recover(Key, Pred),
	arg(2, Pred, Name),
	arg(10, Pred, MailAddress),
	db_url(URL),
	atom_concat([URL, 'webdb_client.cgi?table_name=Software_Description_Request&Software_Description_Request_1=', Key, '&service=query_modify_service&action=amos_search&validated=yes'], ValidatedURL),
	send_mail(edit, Name, MailAddress, ValidatedURL, [], []),
	text_option(send_to_author_title,_, Title),
	text_option(send_to_author_message,_, Message),
	short_response(Title, Message, Response).	

delete_package(Input, Response) :-
	get_form_value(Input, key, Key),
	del_pkg(Key),
	text_option(delete_package_title,_, Title),
	text_option(delete_package_message,_, Message),
	short_response(Title, Message, Response).	

:- pred create_ini_page # "Creates both the initial page for database 
	maintenance and the front page for common user interaction.".

create_ini_page:-
% Maintenance part
       text_option(maintenance_title,_, Title),
       text_option(maintenance_heading,_, Heading),
       html_option(common_footer,_, [], Footer),
       html_option(main_page_heading,_, [Heading], Head),
       html_option(page_body,_, [[Head, Content, Footer]], Body),
       html_option(admin_content,_, [Links], Content),
       db_os(OS),
       (OS = 'Unix' ->
	 StartDbserver = 'admin/startdbserver.cgi',
	 Db_maintenance = 'admin/db_maintenance.cgi',
	 DbServerstop = 'admin/dbserverstop.cgi',
	 WebDBFront = 'webdb_front.cgi'
	;
	 StartDbserver = 'admin/startdbserver.cpx',
	 Db_maintenance = 'admin/db_maintenance.cpx',
	 DbServerstop = 'admin/dbserverstop.cpx',
	 WebDBFront = 'webdb_front.cpx'
	),
        maintenance_link(start_system_link, StartDbserver, StartLink),
        maintenance_link(maintenance_system_link, Db_maintenance, MaintenanceLink),	
        maintenance_link(stop_system_link, DbServerstop, StopLink),
        Links = [StartLink,
                 MaintenanceLink,
	         StopLink],
        full_reply_page(Title, Body, [_|Page]),
        current_executable(Exe),
        absolute_file_name(Exe,'','','.',_,_,Dir),
        atom_concat(Dir,'/maintenance.html',Maintenance_file),
	 umask(_, 8'113),
	 open_output(Maintenance_file, IO),
	 output_html(Page),
	 close_output(IO),

% Client part. Out by the moment due to AMOS particular needs.
%         text_option(webdb_title,_, WTitle),
%         html_option(main_page_heading,_, [''], WHead),
% 	 html_option(page_body,_, [[WHead, WContent, Footer]], WBody),
% 	 html_option(admin_content,_, [WebDBLink], WContent),
% 	 maintenance_link(webdb_system_link, WebDBFront, WebDBLink),	
% 	 full_reply_page(WTitle, WBody, [_|WPage]),
% 	 db_dir(WDir),
% 	 atom_concat(WDir,'/index.html',WebDB_file),
% 	 umask(_, 8'113),
% 	 open_output(WebDB_file, WIO),
% 	 output_html(WPage),
% 	 close_output(WIO),

% Info part
	 dbtable('Software_Description_Request', SArity, SDesc,_,_),
	 generate_info('Software_Description_Request', 2, SArity, SDesc,Info),
	 text_option(info_title,_, ITitle),
	 html_option(main_page_heading,_, [''], IHead),
	 html_option(page_body,_, [[IHead, IContent, Footer]], IBody),
	 html_option(admin_content,_, [Info], IContent),
	 full_reply_page(ITitle, IBody, [_|IPage]),
	 db_dir(WDir),
	 atom_concat(WDir,'/info.html',Info_file),
	 umask(_, 8'113),
	 open_output(Info_file, IIO),
	 output_html(IPage),
	 close_output(IIO) .

maintenance_link(Name, CGI, Link) :-
        text_option(Name,_, Text),
        html_option(maintenance_link,_,[CGI,Text], Link).

generate_info(_TName, N, Arity, _Desc, []) :-
	N > Arity.
generate_info(TName, N, Arity, Desc, Results) :-
	atom_number(AN, N),
	atom_concat([TName, '_', AN], Anchor),
	arg(N, Desc, Field),
	arg(1, Field, FieldName),
	(FieldName = '' ->
	 Results = Info
	;
	 (text_option(FieldName,_,FieldNameDesc) ->
	  html_option(anchor_link,_,[Anchor, FieldName, FieldNameDesc], I)
	 ;
	  html_option(anchor_link,_,[Anchor, '', ''], I) ),
	 Results = [I|Info]),
	 N1 is N + 1,
	generate_info(TName, N1, Arity, Desc, Info).

platform_cgi(Service, CGI) :-
	db_os(OS),
	(OS = 'Unix' ->
	 atom_concat(Service, '.cgi', CGI)
	;
	 atom_concat(Service, '.cpx', CGI)).

ensure_http_header(V, '') :- var(V), !.
ensure_http_header('', '') :- !.
ensure_http_header(Value, PValue) :-
	(atom_concat('http://', _, Value) ->
	 PValue = Value
	;
	 atom_concat('http://', Value, PValue)).

get_entry(Entry):-
	retrieve(_Key, ditem(_Key, AEntry,_Desc)), name(AEntry, SEntry),
 	         remove_dir(SEntry, REntry), name(Entry, REntry).

generate_menus(Values0, Field, N, M, Menus) :-
% 	findall(Entry, (retrieve(_Key,ditem(_Key, AEntry,_Desc)),
% 	                name(AEntry, SEntry),
% 	                remove_dir(SEntry, REntry),
% 			name(Entry, REntry)
% 		       ),   
% 	       Entries),
	setof(Entry, get_entry(Entry), Entries),
	length(Entries, Length),
	Size is Length/M, 
	split(Entries, Size, Ls),
	atom_number(AN, N),
	(var(Values0) -> 
	 Values = []
	;
	 Values = Values0),
	do_generate_menus(Ls, Values, Field, 1, AN, Menus).

do_generate_menus([], _Values, _Field, _I, _N, []).
do_generate_menus([L|Ls], Values, Field, I, N, [Menu|Ms]) :-
	atom_number(AI, I),
	atom_concat([Field, '_menu_', AI], Name),
	process_selected(L, Values, Bucket),
	Menu = select([name=Name, multiple, size=N],Bucket), 
	I1 is I + 1,
	do_generate_menus(Ls, Values, Field, I1, N, Ms).

process_selected([], _Values, []).
process_selected([O|Os], Values, [B|Bs]) :-
	(member(O, Values) ->
	 B = option([selected, value=O], O)
	;
	 B = option([value=O], O)),
	process_selected(Os, Values, Bs).

split([], _Size, []) :- !.
split(Words, Size, [L|Ls]) :-
	extract(Words, 0, Size, L, R),
	split(R, Size, Ls).

extract([],_I,_Size,[],[]) :- !.	
extract([W|Ws], I, Size, [W|R], WR) :-
	I < Size, !,
	I1 is I + 1,
	extract(Ws, I1, Size, R, WR).
extract(Ws, _I, _Size, [], Ws).


generalization_warning(GFlag, '') :-
	var(GFlag), !.
generalization_warning(ko, M) :- !,
	text_option(generalization_does_not_exist,_,M).
generalization_warning(ok, '').

% -----------------------------------------------------------------------------

:- comment(version_maintenance,dir('../version')).

:- comment(version(0*5+19,2004/05/13,00:21*12+'CEST'), "Inserted
   DOCTYPE in html generation (David Trallero Mena)").

:- comment(version(0*5+18,2003/03/11,19:24*48+'CET'), "Administrative
   Interface Prototype functional (Jose Manuel Gomez Perez)").

:- comment(version(0*5+17,2003/02/19,11:02*35+'CET'), "Prototype adaptation
   for AMOS complete (Jose Manuel Gomez Perez)").

:- comment(version(0*5+15,2003/01/08,13:32*17+'CET'), "Distribution updated
 for Windows (Jose Manuel Gomez Perez)").

:- comment(version(0*5+16,2003/01/09,16:52*41+'CET'), "CGI generation made
   platform independent (Jose Manuel Gomez Perez)").

:- comment(version(0*5+14,2002/04/15,11:27*06+'CEST'), "Corrected bug at
   numbers representation (Jose Manuel Gomez Perez)").

:- comment(version(0*5+13,2001/10/15,17:37*55+'CEST'), "Installation
   process and documentation updated (Jose Manuel Gomez Perez)").

:- comment(version(0*5+12,2001/03/23,17:48*27+'CET'), "Distribution process
   revised (Jose Manuel Gomez Perez)").

:- comment(version(0*5+0,2001/02/14,15:44*04+'CET'), "Due to changes in
   persdb, static persistent predicates also need to be declared
   dynamically.  (Jose Manuel Gomez Perez)").

:- comment(version(0*5+11,1999/09/16,12:52*05+'MEST'), "Added lock file to
   prevent the system from being started several times (Jose Manuel Gomez
   Perez)").

:- comment(version(0*5+10,1999/09/03,10:58*16+'MEST'), "Generic client
   interface added (Jose Manuel Gomez Perez)").

:- comment(version(0*5+8,1999/05/31,17:11*11+'MEST'), "Readmes now
automatically generated from documentation.  (Manuel Hermenegildo)").

:- comment(version(0*5+7,1999/04/23,13:14*44+'MEST'), "Naive graphical
statistics added (Jose Manuel Gomez Perez)").

:- comment(version(0*5+6,1999/04/21,11:58*39+'MEST'), "Text statistics
added (Jose Manuel Gomez Perez)").

:- comment(version(0*5+5,1999/04/20,13:09*27+'MEST'), "Infrastructure for
system status info added (Jose Manuel Gomez Perez)").

:- comment(version(0*5+4,1999/03/29,19:57*47+'MEST'), "HTML templates
revised (Jose Manuel Gomez Perez)").

:- comment(version(0*5+3,1999/03/23,18:58*21+'MET'), "Automatic link to
remote SQL databases through ODBC added (Jose Manuel Gomez Perez)").

:- comment(version(0*5+2,1999/02/10,12:29*33+'MET'), "Corrected lack of
generality at the edit tables interface (Jose Manuel Gomez Perez)").

:- comment(version(0*5+1,1999/02/10,12:28*51+'MET'), "Complex query with
restricted preds. completed (Jose Manuel Gomez Perez)").

:- comment(version(0*5+0,1998/07/23,13:26*00+'MET DST'), "Started
   autodocumentation.  (Manuel Hermenegildo)").

:- comment(version(0*1+1,1998/3/18), "Updated maintenance interface.  (Jose
   Manuel Gomez Perez)").

:- comment(version(0*1+0,1998/3/18), "Added recursive treatment of
   relations (base case).  (Jose Manuel Gomez Perez)").

:- comment(version(0*1+0,1998/2/20), "Started automatic
   documentation. (Jose Manuel Gomez Perez)").

:- comment(version(0*0+0,1998/2/20), "Started automatic
   documentation. (Jose Manuel Gomez Perez)").

% ----------------------------------------------------------------------------
